@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.
@@ -139,15 +139,19 @@ export default {
139
139
  immediate: true,
140
140
  handler(nv, ov) {
141
141
  // check for matching values
142
- if (nv && ov && nv.length == ov.length) {
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 < nv.length; i++) {
145
- match = match && nv[i] == ov[i];
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
  },
@@ -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': color(page-bg),
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: color(page-bg),
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'),
@@ -198,8 +198,8 @@ export {
198
198
 
199
199
  .px-menu {
200
200
  font-family: get('font-family');
201
- background-color: color(page-bg);
202
- color: color(page-fg);
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
- .px-scrubber {
125
- @at-root :root {
126
- --px-scrubber-height: 4px;
127
- --px-scrubber-bg: #{clr(accent, $alpha: 0.1)};
128
- --px-scrubber-padding: 1em 0;
129
- --px-scrubber-progress-bg: #{clr(accent, 1)};
130
- --px-scrubber-preview-bg: #{clr(accent, $alpha: 0.33)};
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
+ };