@diabolic/pointy 1.1.1 → 1.2.0
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/README.md +80 -2
- package/dist/pointy.d.ts +150 -10
- package/dist/pointy.esm.js +243 -52
- package/dist/pointy.js +243 -52
- package/dist/pointy.min.js +1 -1
- package/dist/pointy.min.js.map +1 -1
- package/package.json +1 -1
- package/src/pointy.d.ts +150 -10
- package/src/pointy.js +241 -50
package/src/pointy.js
CHANGED
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
* - classNames {object} - Full override of class names
|
|
50
50
|
* - cssVarPrefix {string} - CSS variable prefix (default: classPrefix)
|
|
51
51
|
* - pointerSvg {string} - Custom SVG for pointer
|
|
52
|
+
* - bubbleBackgroundColor {string} - Custom bubble background color (default: '#0a1551')
|
|
53
|
+
* - bubbleTextColor {string} - Custom bubble text color (default: 'white')
|
|
54
|
+
* - bubbleMaxWidth {string} - Max width for bubble (default: '90vw')
|
|
52
55
|
* - onStepChange {function} - Callback on step change
|
|
53
56
|
* - onComplete {function} - Callback on tour complete
|
|
54
57
|
*
|
|
@@ -147,6 +150,7 @@
|
|
|
147
150
|
*
|
|
148
151
|
* Setters (all emit change events):
|
|
149
152
|
* setEasing(), setAnimationDuration(), setIntroFadeDuration(), setBubbleFadeDuration(),
|
|
153
|
+
* setBubbleBackgroundColor(), setBubbleTextColor(), setBubbleMaxWidth(),
|
|
150
154
|
* setMessageInterval(), setMessageTransitionDuration(), setOffset(), setZIndex(),
|
|
151
155
|
* setStayInViewport(enabled, thresholds?), setDirection(direction),
|
|
152
156
|
* setHorizontalDirection(direction), setVerticalDirection(direction),
|
|
@@ -181,7 +185,7 @@ class Pointy {
|
|
|
181
185
|
static POINTER_SVG = `
|
|
182
186
|
<svg xmlns="http://www.w3.org/2000/svg" width="33" height="33" fill="none" viewBox="0 0 33 33">
|
|
183
187
|
<g filter="url(#pointy-shadow)">
|
|
184
|
-
<path fill="
|
|
188
|
+
<path fill="currentColor" d="m18.65 24.262 6.316-14.905c.467-1.103-.645-2.215-1.748-1.747L8.313 13.925c-1.088.461-1.083 2.004.008 2.459l5.049 2.104c.325.135.583.393.718.718l2.104 5.049c.454 1.09 1.997 1.095 2.458.007"/>
|
|
185
189
|
</g>
|
|
186
190
|
<defs>
|
|
187
191
|
<filter id="pointy-shadow" width="32.576" height="32.575" x="0" y="0" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
|
@@ -260,6 +264,10 @@ class Pointy {
|
|
|
260
264
|
--${vp}-duration: 1000ms;
|
|
261
265
|
--${vp}-easing: cubic-bezier(0, 0.55, 0.45, 1);
|
|
262
266
|
--${vp}-bubble-fade: 500ms;
|
|
267
|
+
--${vp}-bubble-bg: #0a1551;
|
|
268
|
+
--${vp}-bubble-color: white;
|
|
269
|
+
--${vp}-bubble-max-width: min(400px, 90vw);
|
|
270
|
+
--${vp}-pointer-color: #0a1551;
|
|
263
271
|
transition: left var(--${vp}-duration) var(--${vp}-easing), top var(--${vp}-duration) var(--${vp}-easing), opacity 0.3s ease;
|
|
264
272
|
animation: ${cn.container}-float 3s ease-in-out infinite;
|
|
265
273
|
}
|
|
@@ -280,7 +288,8 @@ class Pointy {
|
|
|
280
288
|
.${cn.pointer} {
|
|
281
289
|
width: 33px;
|
|
282
290
|
height: 33px;
|
|
283
|
-
|
|
291
|
+
color: var(--${vp}-pointer-color);
|
|
292
|
+
transition: transform var(--${vp}-duration) var(--${vp}-easing), color 0.3s ease;
|
|
284
293
|
}
|
|
285
294
|
|
|
286
295
|
.${cn.bubble} {
|
|
@@ -288,21 +297,23 @@ class Pointy {
|
|
|
288
297
|
right: 26px;
|
|
289
298
|
left: auto;
|
|
290
299
|
top: 0;
|
|
291
|
-
background:
|
|
292
|
-
color:
|
|
300
|
+
background: var(--${vp}-bubble-bg);
|
|
301
|
+
color: var(--${vp}-bubble-color);
|
|
293
302
|
padding: 4px 12px;
|
|
294
303
|
border-radius: 14px;
|
|
295
304
|
font-size: 14px;
|
|
296
305
|
line-height: 20px;
|
|
297
306
|
font-weight: 400;
|
|
298
307
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.25);
|
|
299
|
-
|
|
308
|
+
width: max-content;
|
|
309
|
+
max-width: var(--${vp}-bubble-max-width);
|
|
300
310
|
overflow: hidden;
|
|
301
|
-
transition: width 0.
|
|
311
|
+
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), height 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform var(--${vp}-duration) var(--${vp}-easing), opacity var(--${vp}-bubble-fade) ease, left var(--${vp}-duration) var(--${vp}-easing), right var(--${vp}-duration) var(--${vp}-easing), background 0.3s ease, color 0.3s ease;
|
|
302
312
|
}
|
|
303
313
|
|
|
304
314
|
.${cn.bubbleText} {
|
|
305
|
-
display:
|
|
315
|
+
display: block;
|
|
316
|
+
word-break: break-word;
|
|
306
317
|
}
|
|
307
318
|
`;
|
|
308
319
|
}
|
|
@@ -342,16 +353,20 @@ class Pointy {
|
|
|
342
353
|
static animateText(element, newContent, duration = 500, bubble = null, onComplete = null) {
|
|
343
354
|
const hideTime = duration * 0.4;
|
|
344
355
|
const revealTime = duration * 0.6;
|
|
356
|
+
const resizeTime = 300; // Match CSS transition
|
|
345
357
|
|
|
346
358
|
// Measure new content dimensions using a hidden container
|
|
347
359
|
let newWidth = null;
|
|
348
360
|
let newHeight = null;
|
|
349
361
|
if (bubble) {
|
|
362
|
+
// Get bubble's max-width for proper measurement of multi-line content
|
|
363
|
+
const bubbleStyles = window.getComputedStyle(bubble);
|
|
364
|
+
const maxWidth = bubbleStyles.maxWidth;
|
|
365
|
+
|
|
350
366
|
const measureDiv = document.createElement('div');
|
|
351
|
-
measureDiv.style.cssText =
|
|
367
|
+
measureDiv.style.cssText = `visibility: hidden; position: absolute; padding: 4px 12px; width: max-content; max-width: ${maxWidth};`;
|
|
352
368
|
Pointy.renderContent(measureDiv, newContent);
|
|
353
369
|
bubble.appendChild(measureDiv);
|
|
354
|
-
// Add horizontal padding (12px left + 12px right = 24px)
|
|
355
370
|
newWidth = measureDiv.offsetWidth;
|
|
356
371
|
newHeight = measureDiv.offsetHeight;
|
|
357
372
|
bubble.removeChild(measureDiv);
|
|
@@ -361,22 +376,26 @@ class Pointy {
|
|
|
361
376
|
const currentHeight = bubble.offsetHeight;
|
|
362
377
|
bubble.style.width = currentWidth + 'px';
|
|
363
378
|
bubble.style.height = currentHeight + 'px';
|
|
379
|
+
|
|
380
|
+
// Force reflow
|
|
381
|
+
bubble.offsetHeight;
|
|
382
|
+
|
|
383
|
+
// Start resizing bubble to new size immediately
|
|
384
|
+
bubble.style.width = newWidth + 'px';
|
|
385
|
+
bubble.style.height = newHeight + 'px';
|
|
364
386
|
}
|
|
365
387
|
|
|
366
388
|
// Phase 1: Hide old text (clip from left, disappears to right)
|
|
367
389
|
element.style.transition = `clip-path ${hideTime}ms ease-in`;
|
|
368
390
|
element.style.clipPath = 'inset(0 0 0 100%)';
|
|
369
391
|
|
|
392
|
+
// Wait for bubble resize AND text hide, then change content
|
|
393
|
+
const contentChangeDelay = Math.max(hideTime, resizeTime);
|
|
394
|
+
|
|
370
395
|
setTimeout(() => {
|
|
371
|
-
// Change content while fully clipped
|
|
396
|
+
// Change content while fully clipped AND bubble is at new size
|
|
372
397
|
Pointy.renderContent(element, newContent);
|
|
373
398
|
|
|
374
|
-
// Animate bubble to new size
|
|
375
|
-
if (bubble && newWidth !== null) {
|
|
376
|
-
bubble.style.width = newWidth + 'px';
|
|
377
|
-
bubble.style.height = newHeight + 'px';
|
|
378
|
-
}
|
|
379
|
-
|
|
380
399
|
// Prepare for reveal (start fully clipped from right)
|
|
381
400
|
element.style.transition = 'none';
|
|
382
401
|
element.style.clipPath = 'inset(0 100% 0 0)';
|
|
@@ -388,16 +407,16 @@ class Pointy {
|
|
|
388
407
|
element.style.transition = `clip-path ${revealTime}ms ease-out`;
|
|
389
408
|
element.style.clipPath = 'inset(0 0 0 0)';
|
|
390
409
|
|
|
391
|
-
// Clear dimensions after
|
|
410
|
+
// Clear explicit dimensions after reveal so bubble can auto-size
|
|
392
411
|
if (bubble) {
|
|
393
412
|
setTimeout(() => {
|
|
394
413
|
bubble.style.width = '';
|
|
395
414
|
bubble.style.height = '';
|
|
396
|
-
}, revealTime +
|
|
415
|
+
}, revealTime + 50);
|
|
397
416
|
}
|
|
398
417
|
|
|
399
418
|
if (onComplete) onComplete();
|
|
400
|
-
},
|
|
419
|
+
}, contentChangeDelay);
|
|
401
420
|
}
|
|
402
421
|
|
|
403
422
|
/**
|
|
@@ -480,6 +499,10 @@ class Pointy {
|
|
|
480
499
|
this.autoplayWaitForMessages = options.autoplayWaitForMessages !== undefined ? options.autoplayWaitForMessages : true; // Wait for all messages before advancing
|
|
481
500
|
this.hideOnComplete = options.hideOnComplete !== undefined ? options.hideOnComplete : true; // Auto-hide after tour completes
|
|
482
501
|
this.hideOnCompleteDelay = options.hideOnCompleteDelay !== undefined ? options.hideOnCompleteDelay : null; // Delay before hide (null = use animationDuration)
|
|
502
|
+
this.bubbleBackgroundColor = options.bubbleBackgroundColor || null; // Custom bubble background color
|
|
503
|
+
this.bubbleTextColor = options.bubbleTextColor || null; // Custom bubble text color
|
|
504
|
+
this.bubbleMaxWidth = options.bubbleMaxWidth || null; // Max width for bubble (default: min(400px, 90vw))
|
|
505
|
+
this.pointerColor = options.pointerColor || null; // Custom pointer/cursor color
|
|
483
506
|
this._autoplayTimeoutId = null;
|
|
484
507
|
this._autoplayPaused = false;
|
|
485
508
|
this._messagesCompletedForStep = false; // Track if all messages have been shown
|
|
@@ -504,6 +527,10 @@ class Pointy {
|
|
|
504
527
|
this._lastDirectionChangeTime = 0; // Debounce direction changes
|
|
505
528
|
this.manualHorizontalDirection = null; // 'left', 'right', or null (auto)
|
|
506
529
|
this.manualVerticalDirection = null; // 'up', 'down', or null (auto)
|
|
530
|
+
this._autoDirectionLocked = false; // Lock auto-direction after first calculation per target
|
|
531
|
+
this._lastTargetElement = null; // Track target changes to recalculate direction
|
|
532
|
+
this._bubbleConstraintApplied = false; // Track if bubble constraint has been calculated
|
|
533
|
+
this._cachedBubbleNaturalWidth = null; // Cache bubble's natural width before constraint
|
|
507
534
|
this.moveTimeout = null;
|
|
508
535
|
this._hasShownBefore = false; // For intro animation
|
|
509
536
|
|
|
@@ -520,6 +547,20 @@ class Pointy {
|
|
|
520
547
|
this.container.style.setProperty(`--${this.cssVarPrefix}-easing`, this._resolveEasing(this.easing));
|
|
521
548
|
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-fade`, `${this.bubbleFadeDuration}ms`);
|
|
522
549
|
|
|
550
|
+
// Apply custom bubble colors if provided
|
|
551
|
+
if (this.bubbleBackgroundColor) {
|
|
552
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-bg`, this.bubbleBackgroundColor);
|
|
553
|
+
}
|
|
554
|
+
if (this.bubbleTextColor) {
|
|
555
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-color`, this.bubbleTextColor);
|
|
556
|
+
}
|
|
557
|
+
if (this.bubbleMaxWidth) {
|
|
558
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-max-width`, this.bubbleMaxWidth);
|
|
559
|
+
}
|
|
560
|
+
if (this.pointerColor) {
|
|
561
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-pointer-color`, this.pointerColor);
|
|
562
|
+
}
|
|
563
|
+
|
|
523
564
|
// Apply floating animation setting
|
|
524
565
|
if (!this.floatingAnimation) {
|
|
525
566
|
this.container.style.animationPlayState = 'paused';
|
|
@@ -655,46 +696,47 @@ class Pointy {
|
|
|
655
696
|
this.lastTargetY = currentTargetY;
|
|
656
697
|
}
|
|
657
698
|
|
|
658
|
-
//
|
|
659
|
-
|
|
660
|
-
|
|
699
|
+
// Check if target changed - if so, unlock auto direction and bubble constraint
|
|
700
|
+
if (this._lastTargetElement !== this.targetElement) {
|
|
701
|
+
this._autoDirectionLocked = false;
|
|
702
|
+
this._bubbleConstraintApplied = false;
|
|
703
|
+
this._cachedBubbleNaturalWidth = null;
|
|
704
|
+
this.bubble.style.maxWidth = ''; // Reset constraint for new target
|
|
705
|
+
this._lastTargetElement = this.targetElement;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Stay in viewport: calculate optimal direction ONCE per target, then lock it
|
|
709
|
+
// This prevents flip-flopping during animations and tracking
|
|
710
|
+
if (this.stayInViewport && !this._autoDirectionLocked) {
|
|
661
711
|
const prevIsPointingLeft = this.isPointingLeft;
|
|
662
712
|
const prevIsPointingUp = this.isPointingUp;
|
|
663
713
|
|
|
664
|
-
// Horizontal
|
|
714
|
+
// Horizontal direction (only if not manually set)
|
|
665
715
|
if (this.manualHorizontalDirection === null) {
|
|
666
|
-
|
|
667
|
-
const
|
|
716
|
+
// Calculate available space on each side
|
|
717
|
+
const spaceOnLeft = targetRect.left - this.viewportThresholdX;
|
|
718
|
+
const spaceOnRight = viewportWidth - targetRect.right - this.viewportThresholdX;
|
|
668
719
|
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
// Flip to left side if bubble goes off right edge
|
|
674
|
-
else if (bubbleRightIfPointingRight > viewportWidth && !this.isPointingLeft) {
|
|
675
|
-
this.isPointingLeft = true;
|
|
676
|
-
}
|
|
677
|
-
// Return to default (left) if there's room
|
|
678
|
-
else if (bubbleLeftIfPointingLeft >= 0 && !this.isPointingLeft) {
|
|
679
|
-
this.isPointingLeft = true;
|
|
680
|
-
}
|
|
720
|
+
// Pick the side with more space
|
|
721
|
+
// Add a small preference for left (default) when spaces are similar
|
|
722
|
+
const leftPreference = 20;
|
|
723
|
+
this.isPointingLeft = spaceOnLeft + leftPreference >= spaceOnRight;
|
|
681
724
|
}
|
|
682
725
|
|
|
683
|
-
// Vertical
|
|
726
|
+
// Vertical direction (only if not manually set)
|
|
684
727
|
if (this.manualVerticalDirection === null) {
|
|
685
|
-
|
|
686
|
-
const
|
|
728
|
+
// Calculate available space above and below
|
|
729
|
+
const spaceBelow = viewportHeight - targetRect.bottom - this.viewportThresholdY;
|
|
730
|
+
const spaceAbove = targetRect.top - this.viewportThresholdY;
|
|
687
731
|
|
|
688
|
-
//
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
// Flip to pointing up if bubble goes off top edge
|
|
693
|
-
else if (bubbleTopIfPointingDown < 0 && !this.isPointingUp) {
|
|
694
|
-
this.isPointingUp = true;
|
|
695
|
-
}
|
|
732
|
+
// Pick the side with more space, slight preference for below (pointing up)
|
|
733
|
+
const belowPreference = 10;
|
|
734
|
+
this.isPointingUp = spaceBelow + belowPreference >= spaceAbove;
|
|
696
735
|
}
|
|
697
736
|
|
|
737
|
+
// Lock direction - won't recalculate until target changes
|
|
738
|
+
this._autoDirectionLocked = true;
|
|
739
|
+
|
|
698
740
|
// Emit flip events if direction changed
|
|
699
741
|
if (prevIsPointingLeft !== this.isPointingLeft) {
|
|
700
742
|
this._emit('flipHorizontal', {
|
|
@@ -756,8 +798,77 @@ class Pointy {
|
|
|
756
798
|
|
|
757
799
|
this.pointer.style.transform = pointerRotation;
|
|
758
800
|
this.bubble.style.transform = bubbleTransform;
|
|
801
|
+
|
|
802
|
+
// Clamp container position to keep pointer visible in viewport
|
|
803
|
+
const minLeft = scrollX + 8;
|
|
804
|
+
const maxLeft = scrollX + viewportWidth - 40;
|
|
805
|
+
left = Math.max(minLeft, Math.min(left, maxLeft));
|
|
806
|
+
|
|
759
807
|
this.container.style.left = `${left}px`;
|
|
760
808
|
this.container.style.top = `${top}px`;
|
|
809
|
+
|
|
810
|
+
// Ensure bubble doesn't overflow viewport horizontally
|
|
811
|
+
// Pass the viewport-relative position for constraint calculation
|
|
812
|
+
const viewportLeft = left - scrollX;
|
|
813
|
+
this._constrainBubbleToViewport(viewportLeft, viewportWidth);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* Constrain bubble width and position to prevent horizontal viewport overflow
|
|
818
|
+
* @private
|
|
819
|
+
*/
|
|
820
|
+
_constrainBubbleToViewport(containerLeft, viewportWidth) {
|
|
821
|
+
// Only calculate constraint once per target to prevent oscillation
|
|
822
|
+
if (this._bubbleConstraintApplied) return;
|
|
823
|
+
|
|
824
|
+
const padding = 8; // Minimum padding from viewport edge
|
|
825
|
+
|
|
826
|
+
// Temporarily remove any existing constraint to measure natural width
|
|
827
|
+
const previousMaxWidth = this.bubble.style.maxWidth;
|
|
828
|
+
this.bubble.style.maxWidth = '';
|
|
829
|
+
|
|
830
|
+
// Force layout reflow to get accurate measurement
|
|
831
|
+
const bubbleNaturalWidth = this.bubble.offsetWidth || 100;
|
|
832
|
+
|
|
833
|
+
// Cache the natural width
|
|
834
|
+
this._cachedBubbleNaturalWidth = bubbleNaturalWidth;
|
|
835
|
+
|
|
836
|
+
let needsConstraint = false;
|
|
837
|
+
let constrainedWidth = 0;
|
|
838
|
+
|
|
839
|
+
if (this.isPointingLeft) {
|
|
840
|
+
// Bubble extends to the left of pointer
|
|
841
|
+
// Bubble's left edge = containerLeft - bubbleWidth + 26 (right offset of bubble)
|
|
842
|
+
const bubbleLeftEdge = containerLeft - bubbleNaturalWidth + 26;
|
|
843
|
+
|
|
844
|
+
if (bubbleLeftEdge < padding) {
|
|
845
|
+
// Bubble would overflow left edge - constrain its width
|
|
846
|
+
const availableWidth = containerLeft + 26 - padding;
|
|
847
|
+
constrainedWidth = Math.max(availableWidth, 80);
|
|
848
|
+
needsConstraint = true;
|
|
849
|
+
}
|
|
850
|
+
} else {
|
|
851
|
+
// Bubble extends to the right of pointer
|
|
852
|
+
// Bubble's right edge = containerLeft + 26 + bubbleWidth
|
|
853
|
+
const bubbleRightEdge = containerLeft + 26 + bubbleNaturalWidth;
|
|
854
|
+
|
|
855
|
+
if (bubbleRightEdge > viewportWidth - padding) {
|
|
856
|
+
// Bubble would overflow right edge - constrain its width
|
|
857
|
+
const availableWidth = viewportWidth - containerLeft - 26 - padding;
|
|
858
|
+
constrainedWidth = Math.max(availableWidth, 80);
|
|
859
|
+
needsConstraint = true;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (needsConstraint) {
|
|
864
|
+
this.bubble.style.maxWidth = `${constrainedWidth}px`;
|
|
865
|
+
} else {
|
|
866
|
+
// Restore previous or let CSS variable handle it
|
|
867
|
+
this.bubble.style.maxWidth = previousMaxWidth || '';
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Mark constraint as applied for this target
|
|
871
|
+
this._bubbleConstraintApplied = true;
|
|
761
872
|
}
|
|
762
873
|
|
|
763
874
|
show() {
|
|
@@ -1044,6 +1155,10 @@ class Pointy {
|
|
|
1044
1155
|
this.currentMessages = Array.isArray(firstStep.content) ? firstStep.content : [firstStep.content];
|
|
1045
1156
|
this.currentMessageIndex = 0;
|
|
1046
1157
|
Pointy.renderContent(this.bubbleText, this.currentMessages[0]);
|
|
1158
|
+
this._autoDirectionLocked = false; // Unlock direction for recalculation
|
|
1159
|
+
this._bubbleConstraintApplied = false;
|
|
1160
|
+
this._cachedBubbleNaturalWidth = null;
|
|
1161
|
+
this.bubble.style.maxWidth = '';
|
|
1047
1162
|
}
|
|
1048
1163
|
|
|
1049
1164
|
// After animation completes
|
|
@@ -1685,6 +1800,74 @@ class Pointy {
|
|
|
1685
1800
|
this._emit('bubbleFadeDurationChange', { from: oldDuration, to: duration });
|
|
1686
1801
|
}
|
|
1687
1802
|
|
|
1803
|
+
/**
|
|
1804
|
+
* Set the bubble background color
|
|
1805
|
+
* @param {string} color - CSS color value (hex, rgb, etc.)
|
|
1806
|
+
*/
|
|
1807
|
+
setBubbleBackgroundColor(color) {
|
|
1808
|
+
const oldColor = this.bubbleBackgroundColor;
|
|
1809
|
+
if (oldColor === color) return;
|
|
1810
|
+
|
|
1811
|
+
this.bubbleBackgroundColor = color;
|
|
1812
|
+
if (color) {
|
|
1813
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-bg`, color);
|
|
1814
|
+
} else {
|
|
1815
|
+
this.container.style.removeProperty(`--${this.cssVarPrefix}-bubble-bg`);
|
|
1816
|
+
}
|
|
1817
|
+
this._emit('bubbleBackgroundColorChange', { from: oldColor, to: color });
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Set the bubble text color
|
|
1822
|
+
* @param {string} color - CSS color value (hex, rgb, etc.)
|
|
1823
|
+
*/
|
|
1824
|
+
setBubbleTextColor(color) {
|
|
1825
|
+
const oldColor = this.bubbleTextColor;
|
|
1826
|
+
if (oldColor === color) return;
|
|
1827
|
+
|
|
1828
|
+
this.bubbleTextColor = color;
|
|
1829
|
+
if (color) {
|
|
1830
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-color`, color);
|
|
1831
|
+
} else {
|
|
1832
|
+
this.container.style.removeProperty(`--${this.cssVarPrefix}-bubble-color`);
|
|
1833
|
+
}
|
|
1834
|
+
this._emit('bubbleTextColorChange', { from: oldColor, to: color });
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
/**
|
|
1838
|
+
* Set the bubble max width
|
|
1839
|
+
* @param {string} width - CSS width value (e.g., '90vw', '300px')
|
|
1840
|
+
*/
|
|
1841
|
+
setBubbleMaxWidth(width) {
|
|
1842
|
+
const oldWidth = this.bubbleMaxWidth;
|
|
1843
|
+
if (oldWidth === width) return;
|
|
1844
|
+
|
|
1845
|
+
this.bubbleMaxWidth = width;
|
|
1846
|
+
if (width) {
|
|
1847
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-max-width`, width);
|
|
1848
|
+
} else {
|
|
1849
|
+
this.container.style.removeProperty(`--${this.cssVarPrefix}-bubble-max-width`);
|
|
1850
|
+
}
|
|
1851
|
+
this._emit('bubbleMaxWidthChange', { from: oldWidth, to: width });
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
/**
|
|
1855
|
+
* Set the pointer/cursor color
|
|
1856
|
+
* @param {string} color - CSS color value (hex, rgb, etc.)
|
|
1857
|
+
*/
|
|
1858
|
+
setPointerColor(color) {
|
|
1859
|
+
const oldColor = this.pointerColor;
|
|
1860
|
+
if (oldColor === color) return;
|
|
1861
|
+
|
|
1862
|
+
this.pointerColor = color;
|
|
1863
|
+
if (color) {
|
|
1864
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-pointer-color`, color);
|
|
1865
|
+
} else {
|
|
1866
|
+
this.container.style.removeProperty(`--${this.cssVarPrefix}-pointer-color`);
|
|
1867
|
+
}
|
|
1868
|
+
this._emit('pointerColorChange', { from: oldColor, to: color });
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1688
1871
|
/**
|
|
1689
1872
|
* Get the initial position coordinates based on initialPosition setting
|
|
1690
1873
|
* @returns {{x: number, y: number, isPointingUp?: boolean}} - Position coordinates and optional direction
|
|
@@ -1962,9 +2145,13 @@ class Pointy {
|
|
|
1962
2145
|
// Parse direction: can be 'up', 'down', 'left', 'right', 'up-left', 'down-right', etc.
|
|
1963
2146
|
this._parseDirection(step.direction);
|
|
1964
2147
|
|
|
1965
|
-
// Reset velocity tracking for new target
|
|
2148
|
+
// Reset velocity tracking and direction lock for new target
|
|
1966
2149
|
this._targetYHistory = [];
|
|
1967
2150
|
this.lastTargetY = null;
|
|
2151
|
+
this._autoDirectionLocked = false;
|
|
2152
|
+
this._bubbleConstraintApplied = false;
|
|
2153
|
+
this._cachedBubbleNaturalWidth = null;
|
|
2154
|
+
this.bubble.style.maxWidth = '';
|
|
1968
2155
|
|
|
1969
2156
|
// Pause floating animation during movement
|
|
1970
2157
|
this.container.classList.add(this.classNames.moving);
|
|
@@ -2286,9 +2473,13 @@ class Pointy {
|
|
|
2286
2473
|
// Parse direction (null means auto)
|
|
2287
2474
|
this._parseDirection(direction);
|
|
2288
2475
|
|
|
2289
|
-
// Reset velocity tracking for new target
|
|
2476
|
+
// Reset velocity tracking and direction lock for new target
|
|
2290
2477
|
this._targetYHistory = [];
|
|
2291
2478
|
this.lastTargetY = null;
|
|
2479
|
+
this._autoDirectionLocked = false;
|
|
2480
|
+
this._bubbleConstraintApplied = false;
|
|
2481
|
+
this._cachedBubbleNaturalWidth = null;
|
|
2482
|
+
this.bubble.style.maxWidth = '';
|
|
2292
2483
|
|
|
2293
2484
|
// Pause floating animation during movement
|
|
2294
2485
|
this.container.classList.add(this.classNames.moving);
|