@thinkpixellab-public/px-vue 4.1.24 → 4.1.27
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/bases/PxBaseItemsSelect.vue +7 -3
- package/components/PxCard.vue +1 -1
- package/components/PxCorners.vue +233 -0
- package/components/PxDatePicker.vue +1 -1
- package/components/PxMenu.vue +2 -2
- package/components/PxScrubber.vue +8 -8
- package/composables/loadGsap.js +60 -0
- package/composables/loadLenis.js +82 -0
- package/composables/useCornerSmoothing.js +102 -113
- package/composables/useGsap.js +81 -0
- package/composables/useLenis.js +86 -0
- package/composables/useScrollAnimations.js +142 -0
- package/package.json +3 -2
- package/stories/PxCorners.stories.js +187 -0
- package/storybook/sb-styles.scss +11 -6
- package/utils/smootherCorners.js +395 -0
|
@@ -139,15 +139,19 @@ export default {
|
|
|
139
139
|
immediate: true,
|
|
140
140
|
handler(nv, ov) {
|
|
141
141
|
// check for matching values
|
|
142
|
-
|
|
142
|
+
const nvArray = ensureSet(nv);
|
|
143
|
+
const ovArray = ensureSet(ov);
|
|
144
|
+
|
|
145
|
+
if (nvArray.length === ovArray.length) {
|
|
143
146
|
let match = true;
|
|
144
|
-
for (let i = 0; i <
|
|
145
|
-
match = match &&
|
|
147
|
+
for (let i = 0; i < nvArray.length; i++) {
|
|
148
|
+
match = match && nvArray[i] === ovArray[i];
|
|
146
149
|
}
|
|
147
150
|
if (match) {
|
|
148
151
|
return;
|
|
149
152
|
}
|
|
150
153
|
}
|
|
154
|
+
|
|
151
155
|
this.setSelection(nv);
|
|
152
156
|
},
|
|
153
157
|
},
|
package/components/PxCard.vue
CHANGED
|
@@ -21,7 +21,7 @@ export default {
|
|
|
21
21
|
.px-card {
|
|
22
22
|
/// Configuration defautls for px-calendar
|
|
23
23
|
$-defaults: (
|
|
24
|
-
'px-card.background-color':
|
|
24
|
+
'px-card.background-color': site-var(page-bg),
|
|
25
25
|
'px-card.padding': 1rem,
|
|
26
26
|
'px-card.border-radius': 0.5rem,
|
|
27
27
|
'px-card.box-shadow': depth-shadow(20, 0.1, black),
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Describe this component...
|
|
3
|
+
-->
|
|
4
|
+
<template>
|
|
5
|
+
<div
|
|
6
|
+
v-if="smoothingEnabled"
|
|
7
|
+
:class="bem()"
|
|
8
|
+
ref="el"
|
|
9
|
+
:style="{
|
|
10
|
+
position: 'relative',
|
|
11
|
+
clipPath: renderMode === 'clip' ? smoothCornersPath : null,
|
|
12
|
+
mask: renderMode === 'mask' ? smoothCornersPath : null,
|
|
13
|
+
...overrideStyles,
|
|
14
|
+
}"
|
|
15
|
+
>
|
|
16
|
+
<div
|
|
17
|
+
v-if="hasBorder"
|
|
18
|
+
:style="{
|
|
19
|
+
position: 'absolute',
|
|
20
|
+
inset: 0,
|
|
21
|
+
clipPath: renderMode === 'clip' ? borderMaskPath : null,
|
|
22
|
+
mask: renderMode === 'mask' ? borderMaskPath : null,
|
|
23
|
+
backgroundColor: computedBorderColor,
|
|
24
|
+
touchAction: 'none',
|
|
25
|
+
}"
|
|
26
|
+
></div>
|
|
27
|
+
|
|
28
|
+
<slot />
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div v-else>
|
|
32
|
+
<slot />
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
import { getSmoothCornersPath, getBorderMaskClipPath } from '../utils/smootherCorners';
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
name: 'px-corners',
|
|
41
|
+
props: {
|
|
42
|
+
smoothingEnabled: { type: Boolean, default: true },
|
|
43
|
+
smoothing: { type: Number, default: 1 },
|
|
44
|
+
maxRadius: { type: [Number, String], default: Infinity },
|
|
45
|
+
minRadius: { type: [Number, String], default: 0 },
|
|
46
|
+
radius: { type: [Array, Number, String], default: 'auto' },
|
|
47
|
+
borderWidth: { type: [Number, String], default: 'auto' },
|
|
48
|
+
borderColor: { type: String, default: 'auto' },
|
|
49
|
+
|
|
50
|
+
// clip or mask
|
|
51
|
+
renderMode: { type: String, default: 'clip' },
|
|
52
|
+
},
|
|
53
|
+
data() {
|
|
54
|
+
return {
|
|
55
|
+
dimensions: {
|
|
56
|
+
width: 0,
|
|
57
|
+
height: 0,
|
|
58
|
+
},
|
|
59
|
+
computedRadius: null,
|
|
60
|
+
computedBorderWidth: 0,
|
|
61
|
+
computedBorderColor: null,
|
|
62
|
+
resizeObserver: null,
|
|
63
|
+
|
|
64
|
+
// flag that we've detected auto styles
|
|
65
|
+
autoStylesDetected: false,
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
computed: {
|
|
69
|
+
smoothCornersPath() {
|
|
70
|
+
if (
|
|
71
|
+
!this.smoothingEnabled ||
|
|
72
|
+
this.dimensions.width <= 0 ||
|
|
73
|
+
this.dimensions.height <= 0
|
|
74
|
+
) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return getSmoothCornersPath({
|
|
79
|
+
width: this.dimensions.width,
|
|
80
|
+
height: this.dimensions.height,
|
|
81
|
+
radius: this.computedRadius || this.radius,
|
|
82
|
+
smoothing: this.smoothing,
|
|
83
|
+
borderWidth: 0,
|
|
84
|
+
minRadius: this.minRadius,
|
|
85
|
+
maxRadius: this.maxRadius,
|
|
86
|
+
preserveSmoothing: true,
|
|
87
|
+
renderMode: this.renderMode,
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
borderMaskPath() {
|
|
91
|
+
if (
|
|
92
|
+
!this.smoothingEnabled ||
|
|
93
|
+
this.dimensions.width <= 0 ||
|
|
94
|
+
this.dimensions.height <= 0 ||
|
|
95
|
+
this.computedBorderWidth <= 0
|
|
96
|
+
) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return getBorderMaskClipPath({
|
|
101
|
+
width: this.dimensions.width,
|
|
102
|
+
height: this.dimensions.height,
|
|
103
|
+
radius: this.computedRadius || this.radius,
|
|
104
|
+
smoothing: this.smoothing,
|
|
105
|
+
borderWidth: this.computedBorderWidth,
|
|
106
|
+
minRadius: this.minRadius,
|
|
107
|
+
maxRadius: this.maxRadius,
|
|
108
|
+
preserveSmoothing: true,
|
|
109
|
+
renderMode: this.renderMode,
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
hasBorder() {
|
|
113
|
+
return !!this.borderMaskPath;
|
|
114
|
+
},
|
|
115
|
+
overrideStyles() {
|
|
116
|
+
if (!this.smoothingEnabled || !this.autoStylesDetected) return {};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
borderRadius: 'unset',
|
|
120
|
+
borderTopLeftRadius: 'unset',
|
|
121
|
+
borderTopRightRadius: 'unset',
|
|
122
|
+
borderBottomRightRadius: 'unset',
|
|
123
|
+
borderBottomLeftRadius: 'unset',
|
|
124
|
+
border: 'unset',
|
|
125
|
+
borderWidth: 'unset',
|
|
126
|
+
borderStyle: 'unset',
|
|
127
|
+
borderColor: 'unset',
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
mounted() {
|
|
132
|
+
this.$nextTick(() => {
|
|
133
|
+
if (!this.$refs.el) return;
|
|
134
|
+
if (!this.smoothingEnabled) return;
|
|
135
|
+
|
|
136
|
+
this.detectAutoValues();
|
|
137
|
+
this.updateDimensions();
|
|
138
|
+
this.setupResizeObserver();
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
beforeUnmount() {
|
|
142
|
+
if (this.resizeObserver) {
|
|
143
|
+
this.resizeObserver.disconnect();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
methods: {
|
|
147
|
+
parseCssDimension(value) {
|
|
148
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
149
|
+
value = value.trim().split(/\s+/)[0];
|
|
150
|
+
|
|
151
|
+
if (value.endsWith('px')) return parseFloat(value);
|
|
152
|
+
if (value.endsWith('rem')) {
|
|
153
|
+
return (
|
|
154
|
+
parseFloat(value) *
|
|
155
|
+
parseFloat(getComputedStyle(document.documentElement).fontSize)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (value.endsWith('em')) {
|
|
159
|
+
return parseFloat(value) * parseFloat(getComputedStyle(this.$refs.el).fontSize);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return value;
|
|
164
|
+
},
|
|
165
|
+
detectAutoValues() {
|
|
166
|
+
if (!this.$refs.el) return;
|
|
167
|
+
|
|
168
|
+
const cs = getComputedStyle(this.$refs.el);
|
|
169
|
+
|
|
170
|
+
// auto radius detection
|
|
171
|
+
if (this.radius === 'auto') {
|
|
172
|
+
const radiusTopLeft = cs.getPropertyValue('border-top-left-radius') || '0';
|
|
173
|
+
const radiusTopRight = cs.getPropertyValue('border-top-right-radius') || '0';
|
|
174
|
+
const radiusBottomRight = cs.getPropertyValue('border-bottom-right-radius') || '0';
|
|
175
|
+
const radiusBottomLeft = cs.getPropertyValue('border-bottom-left-radius') || '0';
|
|
176
|
+
|
|
177
|
+
this.computedRadius = [
|
|
178
|
+
radiusTopLeft,
|
|
179
|
+
radiusTopRight,
|
|
180
|
+
radiusBottomRight,
|
|
181
|
+
radiusBottomLeft,
|
|
182
|
+
].map(r => this.parseCssDimension(r));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// auto border width detection
|
|
186
|
+
if (this.borderWidth === 'auto') {
|
|
187
|
+
// try border-width first, fallback to border-top-width
|
|
188
|
+
let borderWidth = cs.getPropertyValue('border-width') || '0';
|
|
189
|
+
if (borderWidth === '0' || borderWidth === '0px') {
|
|
190
|
+
borderWidth = cs.getPropertyValue('border-top-width') || '0';
|
|
191
|
+
}
|
|
192
|
+
this.computedBorderWidth = this.parseCssDimension(borderWidth);
|
|
193
|
+
} else {
|
|
194
|
+
this.computedBorderWidth = parseFloat(this.borderWidth) || 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// auto border color detection
|
|
198
|
+
if (this.borderColor === 'auto') {
|
|
199
|
+
this.computedBorderColor = cs.getPropertyValue('border-color') || 'transparent';
|
|
200
|
+
} else {
|
|
201
|
+
this.computedBorderColor = this.borderColor;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// flag that we've detected styles, now we can apply overrides
|
|
205
|
+
this.autoStylesDetected = true;
|
|
206
|
+
},
|
|
207
|
+
updateDimensions() {
|
|
208
|
+
if (!this.$refs.el) return;
|
|
209
|
+
|
|
210
|
+
const rect = this.$refs.el.getBoundingClientRect();
|
|
211
|
+
this.dimensions.width = rect.width;
|
|
212
|
+
this.dimensions.height = rect.height;
|
|
213
|
+
},
|
|
214
|
+
setupResizeObserver() {
|
|
215
|
+
if (!this.$refs.el) return;
|
|
216
|
+
|
|
217
|
+
this.resizeObserver = new ResizeObserver(entries => {
|
|
218
|
+
for (const entry of entries) {
|
|
219
|
+
this.dimensions.width = entry.contentRect.width;
|
|
220
|
+
this.dimensions.height = entry.contentRect.height;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
this.resizeObserver.observe(this.$refs.el);
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<!-- <style lang="scss" scoped>
|
|
231
|
+
// @use '@/styles/include.scss' as *;
|
|
232
|
+
@use '../styles/px.scss' as *;
|
|
233
|
+
</style> -->
|
|
@@ -190,7 +190,7 @@ export default {
|
|
|
190
190
|
$popup: get(
|
|
191
191
|
'controls.popup',
|
|
192
192
|
(
|
|
193
|
-
background-color:
|
|
193
|
+
background-color: site-var(page-bg),
|
|
194
194
|
border-radius: get('controls.base.border-radius'),
|
|
195
195
|
box-shadow: shadow(15),
|
|
196
196
|
font-size: get('controls.base.font-size'),
|
package/components/PxMenu.vue
CHANGED
|
@@ -198,8 +198,8 @@ export {
|
|
|
198
198
|
|
|
199
199
|
.px-menu {
|
|
200
200
|
font-family: get('font-family');
|
|
201
|
-
background-color:
|
|
202
|
-
color:
|
|
201
|
+
background-color: site-var(page-bg);
|
|
202
|
+
color: site-var(page-fg);
|
|
203
203
|
border-radius: get('controls.border-radius');
|
|
204
204
|
overflow: hidden;
|
|
205
205
|
display: flex;
|
|
@@ -121,15 +121,15 @@ function mouseMove(e) {
|
|
|
121
121
|
<style lang="scss">
|
|
122
122
|
@use '../styles/px.scss' as *;
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
124
|
+
@at-root :root {
|
|
125
|
+
--px-scrubber-height: 4px;
|
|
126
|
+
--px-scrubber-bg: #{clr(accent, $alpha: 0.1)};
|
|
127
|
+
--px-scrubber-padding: 1em 0;
|
|
128
|
+
--px-scrubber-progress-bg: #{clr(accent, 1)};
|
|
129
|
+
--px-scrubber-preview-bg: #{clr(accent, $alpha: 0.33)};
|
|
130
|
+
}
|
|
132
131
|
|
|
132
|
+
.px-scrubber {
|
|
133
133
|
width: 100%;
|
|
134
134
|
padding: var(--px-scrubber-padding);
|
|
135
135
|
cursor: pointer;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// composables/loadGsap.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lazy loading utilities for GSAP and ScrollTrigger
|
|
5
|
+
*
|
|
6
|
+
* These functions provide race-condition-safe loading of GSAP libraries.
|
|
7
|
+
* They ensure single initialization even with concurrent calls.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
let gsapPromise = null;
|
|
11
|
+
let scrollTriggerPromise = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads the GSAP library lazily
|
|
15
|
+
*
|
|
16
|
+
* @returns {Promise} Promise that resolves to the GSAP instance
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Direct usage (not recommended - use useGsap composable instead)
|
|
20
|
+
* import { loadGsap } from '@thinkpixellab-public/px-vue/composables/loadGsap';
|
|
21
|
+
*
|
|
22
|
+
* const gsap = await loadGsap();
|
|
23
|
+
* gsap.to('.element', { x: 100 });
|
|
24
|
+
*/
|
|
25
|
+
export const loadGsap = async () => {
|
|
26
|
+
if (!gsapPromise) {
|
|
27
|
+
gsapPromise = import('gsap').then(module => module.gsap);
|
|
28
|
+
}
|
|
29
|
+
return gsapPromise;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Loads GSAP with ScrollTrigger plugin registered
|
|
34
|
+
*
|
|
35
|
+
* @returns {Promise} Promise that resolves to { gsap, ScrollTrigger }
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Direct usage (not recommended - use useScrollAnimation composable instead)
|
|
39
|
+
* import { loadScrollTrigger } from '@thinkpixellab-public/px-vue/composables/loadGsap';
|
|
40
|
+
*
|
|
41
|
+
* const { gsap, ScrollTrigger } = await loadScrollTrigger();
|
|
42
|
+
*
|
|
43
|
+
* gsap.to('.element', {
|
|
44
|
+
* x: 100,
|
|
45
|
+
* scrollTrigger: {
|
|
46
|
+
* trigger: '.element',
|
|
47
|
+
* start: 'top center'
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
50
|
+
*/
|
|
51
|
+
export const loadScrollTrigger = async () => {
|
|
52
|
+
if (!scrollTriggerPromise) {
|
|
53
|
+
scrollTriggerPromise = (async () => {
|
|
54
|
+
const [gsap, { ScrollTrigger }] = await Promise.all([loadGsap(), import('gsap/ScrollTrigger')]);
|
|
55
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
56
|
+
return { gsap, ScrollTrigger };
|
|
57
|
+
})();
|
|
58
|
+
}
|
|
59
|
+
return scrollTriggerPromise;
|
|
60
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// composables/loadLenis.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lazy loading utilities for Lenis smooth scrolling
|
|
5
|
+
*
|
|
6
|
+
* These functions provide race-condition-safe loading and initialization of Lenis.
|
|
7
|
+
* The instance is shared across all components for consistent scroll behavior.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
let lenisPromise = null;
|
|
11
|
+
let lenisInstancePromise = null;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Loads the Lenis class lazily
|
|
15
|
+
*
|
|
16
|
+
* @returns {Promise} Promise that resolves to the Lenis constructor
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // Direct usage (not recommended - use useLenis composable instead)
|
|
20
|
+
* import { loadLenis } from '@thinkpixellab-public/px-vue/composables/loadLenis';
|
|
21
|
+
*
|
|
22
|
+
* const Lenis = await loadLenis();
|
|
23
|
+
* const customLenis = new Lenis({
|
|
24
|
+
* duration: 2,
|
|
25
|
+
* smoothWheel: true
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export const loadLenis = async () => {
|
|
29
|
+
if (!lenisPromise) {
|
|
30
|
+
lenisPromise = import('lenis').then(module => module.default);
|
|
31
|
+
}
|
|
32
|
+
return lenisPromise;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Loads a shared Lenis instance with default configuration
|
|
37
|
+
*
|
|
38
|
+
* Returns the same instance across all calls for consistent scroll behavior.
|
|
39
|
+
* Starts with autoRaf enabled - useScrollAnimation will take over RAF control when needed.
|
|
40
|
+
*
|
|
41
|
+
* @returns {Promise} Promise that resolves to the shared Lenis instance
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* // Direct usage (not recommended - use useLenis composable instead)
|
|
45
|
+
* import { loadLenisInstance } from '@thinkpixellab-public/px-vue/composables/loadLenis';
|
|
46
|
+
*
|
|
47
|
+
* const lenis = await loadLenisInstance();
|
|
48
|
+
*
|
|
49
|
+
* // smooth scroll to element
|
|
50
|
+
* lenis.scrollTo('#section-2', { duration: 1.5 });
|
|
51
|
+
*
|
|
52
|
+
* // listen to scroll events
|
|
53
|
+
* lenis.on('scroll', (e) => {
|
|
54
|
+
* console.log('Scroll position:', e.scroll);
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Configuration details
|
|
59
|
+
* // The shared instance is created with:
|
|
60
|
+
* // {
|
|
61
|
+
* // smoothWheel: false, // disabled for better performance
|
|
62
|
+
* // smoothTouch: false, // disabled for mobile compatibility
|
|
63
|
+
* // autoRaf: true // auto RAF until ScrollTrigger takes over
|
|
64
|
+
* // }
|
|
65
|
+
*/
|
|
66
|
+
export const loadLenisInstance = async () => {
|
|
67
|
+
if (!lenisInstancePromise) {
|
|
68
|
+
lenisInstancePromise = (async () => {
|
|
69
|
+
const Lenis = await loadLenis();
|
|
70
|
+
|
|
71
|
+
const lenisInstance = new Lenis({
|
|
72
|
+
smoothWheel: false,
|
|
73
|
+
smoothTouch: false,
|
|
74
|
+
autoRaf: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return lenisInstance;
|
|
78
|
+
})();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return lenisInstancePromise;
|
|
82
|
+
};
|