@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/dist/pointy.esm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pointy - A lightweight tooltip library with animated pointer
|
|
3
|
-
* @version 1.
|
|
3
|
+
* @version 1.2.0
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
* - classNames {object} - Full override of class names
|
|
55
55
|
* - cssVarPrefix {string} - CSS variable prefix (default: classPrefix)
|
|
56
56
|
* - pointerSvg {string} - Custom SVG for pointer
|
|
57
|
+
* - bubbleBackgroundColor {string} - Custom bubble background color (default: '#0a1551')
|
|
58
|
+
* - bubbleTextColor {string} - Custom bubble text color (default: 'white')
|
|
59
|
+
* - bubbleMaxWidth {string} - Max width for bubble (default: '90vw')
|
|
57
60
|
* - onStepChange {function} - Callback on step change
|
|
58
61
|
* - onComplete {function} - Callback on tour complete
|
|
59
62
|
*
|
|
@@ -152,6 +155,7 @@
|
|
|
152
155
|
*
|
|
153
156
|
* Setters (all emit change events):
|
|
154
157
|
* setEasing(), setAnimationDuration(), setIntroFadeDuration(), setBubbleFadeDuration(),
|
|
158
|
+
* setBubbleBackgroundColor(), setBubbleTextColor(), setBubbleMaxWidth(),
|
|
155
159
|
* setMessageInterval(), setMessageTransitionDuration(), setOffset(), setZIndex(),
|
|
156
160
|
* setStayInViewport(enabled, thresholds?), setDirection(direction),
|
|
157
161
|
* setHorizontalDirection(direction), setVerticalDirection(direction),
|
|
@@ -186,7 +190,7 @@ class Pointy {
|
|
|
186
190
|
static POINTER_SVG = `
|
|
187
191
|
<svg xmlns="http://www.w3.org/2000/svg" width="33" height="33" fill="none" viewBox="0 0 33 33">
|
|
188
192
|
<g filter="url(#pointy-shadow)">
|
|
189
|
-
<path fill="
|
|
193
|
+
<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"/>
|
|
190
194
|
</g>
|
|
191
195
|
<defs>
|
|
192
196
|
<filter id="pointy-shadow" width="32.576" height="32.575" x="0" y="0" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
|
|
@@ -265,6 +269,10 @@ class Pointy {
|
|
|
265
269
|
--${vp}-duration: 1000ms;
|
|
266
270
|
--${vp}-easing: cubic-bezier(0, 0.55, 0.45, 1);
|
|
267
271
|
--${vp}-bubble-fade: 500ms;
|
|
272
|
+
--${vp}-bubble-bg: #0a1551;
|
|
273
|
+
--${vp}-bubble-color: white;
|
|
274
|
+
--${vp}-bubble-max-width: min(400px, 90vw);
|
|
275
|
+
--${vp}-pointer-color: #0a1551;
|
|
268
276
|
transition: left var(--${vp}-duration) var(--${vp}-easing), top var(--${vp}-duration) var(--${vp}-easing), opacity 0.3s ease;
|
|
269
277
|
animation: ${cn.container}-float 3s ease-in-out infinite;
|
|
270
278
|
}
|
|
@@ -285,7 +293,8 @@ class Pointy {
|
|
|
285
293
|
.${cn.pointer} {
|
|
286
294
|
width: 33px;
|
|
287
295
|
height: 33px;
|
|
288
|
-
|
|
296
|
+
color: var(--${vp}-pointer-color);
|
|
297
|
+
transition: transform var(--${vp}-duration) var(--${vp}-easing), color 0.3s ease;
|
|
289
298
|
}
|
|
290
299
|
|
|
291
300
|
.${cn.bubble} {
|
|
@@ -293,21 +302,23 @@ class Pointy {
|
|
|
293
302
|
right: 26px;
|
|
294
303
|
left: auto;
|
|
295
304
|
top: 0;
|
|
296
|
-
background:
|
|
297
|
-
color:
|
|
305
|
+
background: var(--${vp}-bubble-bg);
|
|
306
|
+
color: var(--${vp}-bubble-color);
|
|
298
307
|
padding: 4px 12px;
|
|
299
308
|
border-radius: 14px;
|
|
300
309
|
font-size: 14px;
|
|
301
310
|
line-height: 20px;
|
|
302
311
|
font-weight: 400;
|
|
303
312
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.25);
|
|
304
|
-
|
|
313
|
+
width: max-content;
|
|
314
|
+
max-width: var(--${vp}-bubble-max-width);
|
|
305
315
|
overflow: hidden;
|
|
306
|
-
transition: width 0.
|
|
316
|
+
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;
|
|
307
317
|
}
|
|
308
318
|
|
|
309
319
|
.${cn.bubbleText} {
|
|
310
|
-
display:
|
|
320
|
+
display: block;
|
|
321
|
+
word-break: break-word;
|
|
311
322
|
}
|
|
312
323
|
`;
|
|
313
324
|
}
|
|
@@ -347,16 +358,20 @@ class Pointy {
|
|
|
347
358
|
static animateText(element, newContent, duration = 500, bubble = null, onComplete = null) {
|
|
348
359
|
const hideTime = duration * 0.4;
|
|
349
360
|
const revealTime = duration * 0.6;
|
|
361
|
+
const resizeTime = 300; // Match CSS transition
|
|
350
362
|
|
|
351
363
|
// Measure new content dimensions using a hidden container
|
|
352
364
|
let newWidth = null;
|
|
353
365
|
let newHeight = null;
|
|
354
366
|
if (bubble) {
|
|
367
|
+
// Get bubble's max-width for proper measurement of multi-line content
|
|
368
|
+
const bubbleStyles = window.getComputedStyle(bubble);
|
|
369
|
+
const maxWidth = bubbleStyles.maxWidth;
|
|
370
|
+
|
|
355
371
|
const measureDiv = document.createElement('div');
|
|
356
|
-
measureDiv.style.cssText =
|
|
372
|
+
measureDiv.style.cssText = `visibility: hidden; position: absolute; padding: 4px 12px; width: max-content; max-width: ${maxWidth};`;
|
|
357
373
|
Pointy.renderContent(measureDiv, newContent);
|
|
358
374
|
bubble.appendChild(measureDiv);
|
|
359
|
-
// Add horizontal padding (12px left + 12px right = 24px)
|
|
360
375
|
newWidth = measureDiv.offsetWidth;
|
|
361
376
|
newHeight = measureDiv.offsetHeight;
|
|
362
377
|
bubble.removeChild(measureDiv);
|
|
@@ -366,22 +381,26 @@ class Pointy {
|
|
|
366
381
|
const currentHeight = bubble.offsetHeight;
|
|
367
382
|
bubble.style.width = currentWidth + 'px';
|
|
368
383
|
bubble.style.height = currentHeight + 'px';
|
|
384
|
+
|
|
385
|
+
// Force reflow
|
|
386
|
+
bubble.offsetHeight;
|
|
387
|
+
|
|
388
|
+
// Start resizing bubble to new size immediately
|
|
389
|
+
bubble.style.width = newWidth + 'px';
|
|
390
|
+
bubble.style.height = newHeight + 'px';
|
|
369
391
|
}
|
|
370
392
|
|
|
371
393
|
// Phase 1: Hide old text (clip from left, disappears to right)
|
|
372
394
|
element.style.transition = `clip-path ${hideTime}ms ease-in`;
|
|
373
395
|
element.style.clipPath = 'inset(0 0 0 100%)';
|
|
374
396
|
|
|
397
|
+
// Wait for bubble resize AND text hide, then change content
|
|
398
|
+
const contentChangeDelay = Math.max(hideTime, resizeTime);
|
|
399
|
+
|
|
375
400
|
setTimeout(() => {
|
|
376
|
-
// Change content while fully clipped
|
|
401
|
+
// Change content while fully clipped AND bubble is at new size
|
|
377
402
|
Pointy.renderContent(element, newContent);
|
|
378
403
|
|
|
379
|
-
// Animate bubble to new size
|
|
380
|
-
if (bubble && newWidth !== null) {
|
|
381
|
-
bubble.style.width = newWidth + 'px';
|
|
382
|
-
bubble.style.height = newHeight + 'px';
|
|
383
|
-
}
|
|
384
|
-
|
|
385
404
|
// Prepare for reveal (start fully clipped from right)
|
|
386
405
|
element.style.transition = 'none';
|
|
387
406
|
element.style.clipPath = 'inset(0 100% 0 0)';
|
|
@@ -393,16 +412,16 @@ class Pointy {
|
|
|
393
412
|
element.style.transition = `clip-path ${revealTime}ms ease-out`;
|
|
394
413
|
element.style.clipPath = 'inset(0 0 0 0)';
|
|
395
414
|
|
|
396
|
-
// Clear dimensions after
|
|
415
|
+
// Clear explicit dimensions after reveal so bubble can auto-size
|
|
397
416
|
if (bubble) {
|
|
398
417
|
setTimeout(() => {
|
|
399
418
|
bubble.style.width = '';
|
|
400
419
|
bubble.style.height = '';
|
|
401
|
-
}, revealTime +
|
|
420
|
+
}, revealTime + 50);
|
|
402
421
|
}
|
|
403
422
|
|
|
404
423
|
if (onComplete) onComplete();
|
|
405
|
-
},
|
|
424
|
+
}, contentChangeDelay);
|
|
406
425
|
}
|
|
407
426
|
|
|
408
427
|
/**
|
|
@@ -485,6 +504,10 @@ class Pointy {
|
|
|
485
504
|
this.autoplayWaitForMessages = options.autoplayWaitForMessages !== undefined ? options.autoplayWaitForMessages : true; // Wait for all messages before advancing
|
|
486
505
|
this.hideOnComplete = options.hideOnComplete !== undefined ? options.hideOnComplete : true; // Auto-hide after tour completes
|
|
487
506
|
this.hideOnCompleteDelay = options.hideOnCompleteDelay !== undefined ? options.hideOnCompleteDelay : null; // Delay before hide (null = use animationDuration)
|
|
507
|
+
this.bubbleBackgroundColor = options.bubbleBackgroundColor || null; // Custom bubble background color
|
|
508
|
+
this.bubbleTextColor = options.bubbleTextColor || null; // Custom bubble text color
|
|
509
|
+
this.bubbleMaxWidth = options.bubbleMaxWidth || null; // Max width for bubble (default: min(400px, 90vw))
|
|
510
|
+
this.pointerColor = options.pointerColor || null; // Custom pointer/cursor color
|
|
488
511
|
this._autoplayTimeoutId = null;
|
|
489
512
|
this._autoplayPaused = false;
|
|
490
513
|
this._messagesCompletedForStep = false; // Track if all messages have been shown
|
|
@@ -509,6 +532,10 @@ class Pointy {
|
|
|
509
532
|
this._lastDirectionChangeTime = 0; // Debounce direction changes
|
|
510
533
|
this.manualHorizontalDirection = null; // 'left', 'right', or null (auto)
|
|
511
534
|
this.manualVerticalDirection = null; // 'up', 'down', or null (auto)
|
|
535
|
+
this._autoDirectionLocked = false; // Lock auto-direction after first calculation per target
|
|
536
|
+
this._lastTargetElement = null; // Track target changes to recalculate direction
|
|
537
|
+
this._bubbleConstraintApplied = false; // Track if bubble constraint has been calculated
|
|
538
|
+
this._cachedBubbleNaturalWidth = null; // Cache bubble's natural width before constraint
|
|
512
539
|
this.moveTimeout = null;
|
|
513
540
|
this._hasShownBefore = false; // For intro animation
|
|
514
541
|
|
|
@@ -525,6 +552,20 @@ class Pointy {
|
|
|
525
552
|
this.container.style.setProperty(`--${this.cssVarPrefix}-easing`, this._resolveEasing(this.easing));
|
|
526
553
|
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-fade`, `${this.bubbleFadeDuration}ms`);
|
|
527
554
|
|
|
555
|
+
// Apply custom bubble colors if provided
|
|
556
|
+
if (this.bubbleBackgroundColor) {
|
|
557
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-bg`, this.bubbleBackgroundColor);
|
|
558
|
+
}
|
|
559
|
+
if (this.bubbleTextColor) {
|
|
560
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-color`, this.bubbleTextColor);
|
|
561
|
+
}
|
|
562
|
+
if (this.bubbleMaxWidth) {
|
|
563
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-max-width`, this.bubbleMaxWidth);
|
|
564
|
+
}
|
|
565
|
+
if (this.pointerColor) {
|
|
566
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-pointer-color`, this.pointerColor);
|
|
567
|
+
}
|
|
568
|
+
|
|
528
569
|
// Apply floating animation setting
|
|
529
570
|
if (!this.floatingAnimation) {
|
|
530
571
|
this.container.style.animationPlayState = 'paused';
|
|
@@ -614,7 +655,7 @@ class Pointy {
|
|
|
614
655
|
const scrollY = window.scrollY;
|
|
615
656
|
const viewportWidth = window.innerWidth;
|
|
616
657
|
const viewportHeight = window.innerHeight;
|
|
617
|
-
|
|
658
|
+
this.bubble.offsetWidth || 100;
|
|
618
659
|
const bubbleHeight = this.bubble.offsetHeight || 28;
|
|
619
660
|
|
|
620
661
|
// Manual horizontal direction takes priority
|
|
@@ -660,46 +701,47 @@ class Pointy {
|
|
|
660
701
|
this.lastTargetY = currentTargetY;
|
|
661
702
|
}
|
|
662
703
|
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
|
|
704
|
+
// Check if target changed - if so, unlock auto direction and bubble constraint
|
|
705
|
+
if (this._lastTargetElement !== this.targetElement) {
|
|
706
|
+
this._autoDirectionLocked = false;
|
|
707
|
+
this._bubbleConstraintApplied = false;
|
|
708
|
+
this._cachedBubbleNaturalWidth = null;
|
|
709
|
+
this.bubble.style.maxWidth = ''; // Reset constraint for new target
|
|
710
|
+
this._lastTargetElement = this.targetElement;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Stay in viewport: calculate optimal direction ONCE per target, then lock it
|
|
714
|
+
// This prevents flip-flopping during animations and tracking
|
|
715
|
+
if (this.stayInViewport && !this._autoDirectionLocked) {
|
|
666
716
|
const prevIsPointingLeft = this.isPointingLeft;
|
|
667
717
|
const prevIsPointingUp = this.isPointingUp;
|
|
668
718
|
|
|
669
|
-
// Horizontal
|
|
719
|
+
// Horizontal direction (only if not manually set)
|
|
670
720
|
if (this.manualHorizontalDirection === null) {
|
|
671
|
-
|
|
672
|
-
const
|
|
721
|
+
// Calculate available space on each side
|
|
722
|
+
const spaceOnLeft = targetRect.left - this.viewportThresholdX;
|
|
723
|
+
const spaceOnRight = viewportWidth - targetRect.right - this.viewportThresholdX;
|
|
673
724
|
|
|
674
|
-
//
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
// Flip to left side if bubble goes off right edge
|
|
679
|
-
else if (bubbleRightIfPointingRight > viewportWidth && !this.isPointingLeft) {
|
|
680
|
-
this.isPointingLeft = true;
|
|
681
|
-
}
|
|
682
|
-
// Return to default (left) if there's room
|
|
683
|
-
else if (bubbleLeftIfPointingLeft >= 0 && !this.isPointingLeft) {
|
|
684
|
-
this.isPointingLeft = true;
|
|
685
|
-
}
|
|
725
|
+
// Pick the side with more space
|
|
726
|
+
// Add a small preference for left (default) when spaces are similar
|
|
727
|
+
const leftPreference = 20;
|
|
728
|
+
this.isPointingLeft = spaceOnLeft + leftPreference >= spaceOnRight;
|
|
686
729
|
}
|
|
687
730
|
|
|
688
|
-
// Vertical
|
|
731
|
+
// Vertical direction (only if not manually set)
|
|
689
732
|
if (this.manualVerticalDirection === null) {
|
|
690
|
-
|
|
691
|
-
const
|
|
733
|
+
// Calculate available space above and below
|
|
734
|
+
const spaceBelow = viewportHeight - targetRect.bottom - this.viewportThresholdY;
|
|
735
|
+
const spaceAbove = targetRect.top - this.viewportThresholdY;
|
|
692
736
|
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
// Flip to pointing up if bubble goes off top edge
|
|
698
|
-
else if (bubbleTopIfPointingDown < 0 && !this.isPointingUp) {
|
|
699
|
-
this.isPointingUp = true;
|
|
700
|
-
}
|
|
737
|
+
// Pick the side with more space, slight preference for below (pointing up)
|
|
738
|
+
const belowPreference = 10;
|
|
739
|
+
this.isPointingUp = spaceBelow + belowPreference >= spaceAbove;
|
|
701
740
|
}
|
|
702
741
|
|
|
742
|
+
// Lock direction - won't recalculate until target changes
|
|
743
|
+
this._autoDirectionLocked = true;
|
|
744
|
+
|
|
703
745
|
// Emit flip events if direction changed
|
|
704
746
|
if (prevIsPointingLeft !== this.isPointingLeft) {
|
|
705
747
|
this._emit('flipHorizontal', {
|
|
@@ -761,8 +803,77 @@ class Pointy {
|
|
|
761
803
|
|
|
762
804
|
this.pointer.style.transform = pointerRotation;
|
|
763
805
|
this.bubble.style.transform = bubbleTransform;
|
|
806
|
+
|
|
807
|
+
// Clamp container position to keep pointer visible in viewport
|
|
808
|
+
const minLeft = scrollX + 8;
|
|
809
|
+
const maxLeft = scrollX + viewportWidth - 40;
|
|
810
|
+
left = Math.max(minLeft, Math.min(left, maxLeft));
|
|
811
|
+
|
|
764
812
|
this.container.style.left = `${left}px`;
|
|
765
813
|
this.container.style.top = `${top}px`;
|
|
814
|
+
|
|
815
|
+
// Ensure bubble doesn't overflow viewport horizontally
|
|
816
|
+
// Pass the viewport-relative position for constraint calculation
|
|
817
|
+
const viewportLeft = left - scrollX;
|
|
818
|
+
this._constrainBubbleToViewport(viewportLeft, viewportWidth);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Constrain bubble width and position to prevent horizontal viewport overflow
|
|
823
|
+
* @private
|
|
824
|
+
*/
|
|
825
|
+
_constrainBubbleToViewport(containerLeft, viewportWidth) {
|
|
826
|
+
// Only calculate constraint once per target to prevent oscillation
|
|
827
|
+
if (this._bubbleConstraintApplied) return;
|
|
828
|
+
|
|
829
|
+
const padding = 8; // Minimum padding from viewport edge
|
|
830
|
+
|
|
831
|
+
// Temporarily remove any existing constraint to measure natural width
|
|
832
|
+
const previousMaxWidth = this.bubble.style.maxWidth;
|
|
833
|
+
this.bubble.style.maxWidth = '';
|
|
834
|
+
|
|
835
|
+
// Force layout reflow to get accurate measurement
|
|
836
|
+
const bubbleNaturalWidth = this.bubble.offsetWidth || 100;
|
|
837
|
+
|
|
838
|
+
// Cache the natural width
|
|
839
|
+
this._cachedBubbleNaturalWidth = bubbleNaturalWidth;
|
|
840
|
+
|
|
841
|
+
let needsConstraint = false;
|
|
842
|
+
let constrainedWidth = 0;
|
|
843
|
+
|
|
844
|
+
if (this.isPointingLeft) {
|
|
845
|
+
// Bubble extends to the left of pointer
|
|
846
|
+
// Bubble's left edge = containerLeft - bubbleWidth + 26 (right offset of bubble)
|
|
847
|
+
const bubbleLeftEdge = containerLeft - bubbleNaturalWidth + 26;
|
|
848
|
+
|
|
849
|
+
if (bubbleLeftEdge < padding) {
|
|
850
|
+
// Bubble would overflow left edge - constrain its width
|
|
851
|
+
const availableWidth = containerLeft + 26 - padding;
|
|
852
|
+
constrainedWidth = Math.max(availableWidth, 80);
|
|
853
|
+
needsConstraint = true;
|
|
854
|
+
}
|
|
855
|
+
} else {
|
|
856
|
+
// Bubble extends to the right of pointer
|
|
857
|
+
// Bubble's right edge = containerLeft + 26 + bubbleWidth
|
|
858
|
+
const bubbleRightEdge = containerLeft + 26 + bubbleNaturalWidth;
|
|
859
|
+
|
|
860
|
+
if (bubbleRightEdge > viewportWidth - padding) {
|
|
861
|
+
// Bubble would overflow right edge - constrain its width
|
|
862
|
+
const availableWidth = viewportWidth - containerLeft - 26 - padding;
|
|
863
|
+
constrainedWidth = Math.max(availableWidth, 80);
|
|
864
|
+
needsConstraint = true;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (needsConstraint) {
|
|
869
|
+
this.bubble.style.maxWidth = `${constrainedWidth}px`;
|
|
870
|
+
} else {
|
|
871
|
+
// Restore previous or let CSS variable handle it
|
|
872
|
+
this.bubble.style.maxWidth = previousMaxWidth || '';
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Mark constraint as applied for this target
|
|
876
|
+
this._bubbleConstraintApplied = true;
|
|
766
877
|
}
|
|
767
878
|
|
|
768
879
|
show() {
|
|
@@ -1049,6 +1160,10 @@ class Pointy {
|
|
|
1049
1160
|
this.currentMessages = Array.isArray(firstStep.content) ? firstStep.content : [firstStep.content];
|
|
1050
1161
|
this.currentMessageIndex = 0;
|
|
1051
1162
|
Pointy.renderContent(this.bubbleText, this.currentMessages[0]);
|
|
1163
|
+
this._autoDirectionLocked = false; // Unlock direction for recalculation
|
|
1164
|
+
this._bubbleConstraintApplied = false;
|
|
1165
|
+
this._cachedBubbleNaturalWidth = null;
|
|
1166
|
+
this.bubble.style.maxWidth = '';
|
|
1052
1167
|
}
|
|
1053
1168
|
|
|
1054
1169
|
// After animation completes
|
|
@@ -1690,6 +1805,74 @@ class Pointy {
|
|
|
1690
1805
|
this._emit('bubbleFadeDurationChange', { from: oldDuration, to: duration });
|
|
1691
1806
|
}
|
|
1692
1807
|
|
|
1808
|
+
/**
|
|
1809
|
+
* Set the bubble background color
|
|
1810
|
+
* @param {string} color - CSS color value (hex, rgb, etc.)
|
|
1811
|
+
*/
|
|
1812
|
+
setBubbleBackgroundColor(color) {
|
|
1813
|
+
const oldColor = this.bubbleBackgroundColor;
|
|
1814
|
+
if (oldColor === color) return;
|
|
1815
|
+
|
|
1816
|
+
this.bubbleBackgroundColor = color;
|
|
1817
|
+
if (color) {
|
|
1818
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-bg`, color);
|
|
1819
|
+
} else {
|
|
1820
|
+
this.container.style.removeProperty(`--${this.cssVarPrefix}-bubble-bg`);
|
|
1821
|
+
}
|
|
1822
|
+
this._emit('bubbleBackgroundColorChange', { from: oldColor, to: color });
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* Set the bubble text color
|
|
1827
|
+
* @param {string} color - CSS color value (hex, rgb, etc.)
|
|
1828
|
+
*/
|
|
1829
|
+
setBubbleTextColor(color) {
|
|
1830
|
+
const oldColor = this.bubbleTextColor;
|
|
1831
|
+
if (oldColor === color) return;
|
|
1832
|
+
|
|
1833
|
+
this.bubbleTextColor = color;
|
|
1834
|
+
if (color) {
|
|
1835
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-color`, color);
|
|
1836
|
+
} else {
|
|
1837
|
+
this.container.style.removeProperty(`--${this.cssVarPrefix}-bubble-color`);
|
|
1838
|
+
}
|
|
1839
|
+
this._emit('bubbleTextColorChange', { from: oldColor, to: color });
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
/**
|
|
1843
|
+
* Set the bubble max width
|
|
1844
|
+
* @param {string} width - CSS width value (e.g., '90vw', '300px')
|
|
1845
|
+
*/
|
|
1846
|
+
setBubbleMaxWidth(width) {
|
|
1847
|
+
const oldWidth = this.bubbleMaxWidth;
|
|
1848
|
+
if (oldWidth === width) return;
|
|
1849
|
+
|
|
1850
|
+
this.bubbleMaxWidth = width;
|
|
1851
|
+
if (width) {
|
|
1852
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-bubble-max-width`, width);
|
|
1853
|
+
} else {
|
|
1854
|
+
this.container.style.removeProperty(`--${this.cssVarPrefix}-bubble-max-width`);
|
|
1855
|
+
}
|
|
1856
|
+
this._emit('bubbleMaxWidthChange', { from: oldWidth, to: width });
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
/**
|
|
1860
|
+
* Set the pointer/cursor color
|
|
1861
|
+
* @param {string} color - CSS color value (hex, rgb, etc.)
|
|
1862
|
+
*/
|
|
1863
|
+
setPointerColor(color) {
|
|
1864
|
+
const oldColor = this.pointerColor;
|
|
1865
|
+
if (oldColor === color) return;
|
|
1866
|
+
|
|
1867
|
+
this.pointerColor = color;
|
|
1868
|
+
if (color) {
|
|
1869
|
+
this.container.style.setProperty(`--${this.cssVarPrefix}-pointer-color`, color);
|
|
1870
|
+
} else {
|
|
1871
|
+
this.container.style.removeProperty(`--${this.cssVarPrefix}-pointer-color`);
|
|
1872
|
+
}
|
|
1873
|
+
this._emit('pointerColorChange', { from: oldColor, to: color });
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1693
1876
|
/**
|
|
1694
1877
|
* Get the initial position coordinates based on initialPosition setting
|
|
1695
1878
|
* @returns {{x: number, y: number, isPointingUp?: boolean}} - Position coordinates and optional direction
|
|
@@ -1967,9 +2150,13 @@ class Pointy {
|
|
|
1967
2150
|
// Parse direction: can be 'up', 'down', 'left', 'right', 'up-left', 'down-right', etc.
|
|
1968
2151
|
this._parseDirection(step.direction);
|
|
1969
2152
|
|
|
1970
|
-
// Reset velocity tracking for new target
|
|
2153
|
+
// Reset velocity tracking and direction lock for new target
|
|
1971
2154
|
this._targetYHistory = [];
|
|
1972
2155
|
this.lastTargetY = null;
|
|
2156
|
+
this._autoDirectionLocked = false;
|
|
2157
|
+
this._bubbleConstraintApplied = false;
|
|
2158
|
+
this._cachedBubbleNaturalWidth = null;
|
|
2159
|
+
this.bubble.style.maxWidth = '';
|
|
1973
2160
|
|
|
1974
2161
|
// Pause floating animation during movement
|
|
1975
2162
|
this.container.classList.add(this.classNames.moving);
|
|
@@ -2291,9 +2478,13 @@ class Pointy {
|
|
|
2291
2478
|
// Parse direction (null means auto)
|
|
2292
2479
|
this._parseDirection(direction);
|
|
2293
2480
|
|
|
2294
|
-
// Reset velocity tracking for new target
|
|
2481
|
+
// Reset velocity tracking and direction lock for new target
|
|
2295
2482
|
this._targetYHistory = [];
|
|
2296
2483
|
this.lastTargetY = null;
|
|
2484
|
+
this._autoDirectionLocked = false;
|
|
2485
|
+
this._bubbleConstraintApplied = false;
|
|
2486
|
+
this._cachedBubbleNaturalWidth = null;
|
|
2487
|
+
this.bubble.style.maxWidth = '';
|
|
2297
2488
|
|
|
2298
2489
|
// Pause floating animation during movement
|
|
2299
2490
|
this.container.classList.add(this.classNames.moving);
|