@diabolic/pointy 1.0.1 → 1.0.3
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 +44 -4
- package/dist/pointy.esm.js +114 -19
- package/dist/pointy.js +114 -19
- package/dist/pointy.min.js +1 -1
- package/dist/pointy.min.js.map +1 -1
- package/package.json +1 -1
- package/src/pointy.js +113 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diabolic/pointy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A lightweight, dependency-free JavaScript library for creating animated tooltips with a pointing cursor. Perfect for product tours, onboarding flows, and feature highlights.",
|
|
6
6
|
"main": "dist/pointy.js",
|
package/src/pointy.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* @options
|
|
20
20
|
* - steps {Array<{target, content, direction?, duration?}>} - Tour steps
|
|
21
21
|
* - target {string|HTMLElement} - Initial target element
|
|
22
|
+
* - content {string|string[]} - Initial content/messages (single-step use)
|
|
22
23
|
* - offsetX {number} - Horizontal offset from target (default: 20)
|
|
23
24
|
* - offsetY {number} - Vertical offset from target (default: 16)
|
|
24
25
|
* - trackingFps {number} - Position update FPS, 0 = unlimited (default: 60)
|
|
@@ -73,9 +74,9 @@
|
|
|
73
74
|
* - introAnimationEnd: Initial fade-in animation completed
|
|
74
75
|
*
|
|
75
76
|
* Content:
|
|
76
|
-
* -
|
|
77
|
-
* -
|
|
78
|
-
* - messageChange: Message changed (
|
|
77
|
+
* - messagesSet: Messages array replaced via setMessages()
|
|
78
|
+
* - messageUpdate: Single message updated via setMessage()
|
|
79
|
+
* - messageChange: Message changed (navigation or auto-cycle)
|
|
79
80
|
*
|
|
80
81
|
* Message Cycle:
|
|
81
82
|
* - messageCycleStart: Auto message cycling started
|
|
@@ -126,7 +127,7 @@
|
|
|
126
127
|
* Core: show(), hide(), destroy()
|
|
127
128
|
* Navigation: next(), prev(), goToStep(index), reset(), restart()
|
|
128
129
|
* Custom Target: pointTo(target, content?, direction?)
|
|
129
|
-
* Content:
|
|
130
|
+
* Content: setMessages(content), setMessage(msg), nextMessage(), prevMessage(), goToMessage(index)
|
|
130
131
|
* Message Cycle: startMessageCycle(interval?), stopMessageCycle(), pauseMessageCycle(), resumeMessageCycle()
|
|
131
132
|
* Autoplay: startAutoplay(), stopAutoplay(), pauseAutoplay(), resumeAutoplay()
|
|
132
133
|
* Animation: animateToInitialPosition()
|
|
@@ -501,6 +502,8 @@ class Pointy {
|
|
|
501
502
|
|
|
502
503
|
this.bubble = document.createElement('div');
|
|
503
504
|
this.bubble.className = this.classNames.bubble;
|
|
505
|
+
// Set initial bubble position for pointing up (default)
|
|
506
|
+
this.bubble.style.transform = 'translateY(28px)';
|
|
504
507
|
|
|
505
508
|
this.bubbleText = document.createElement('span');
|
|
506
509
|
this.bubbleText.className = this.classNames.bubbleText;
|
|
@@ -690,7 +693,7 @@ class Pointy {
|
|
|
690
693
|
} else {
|
|
691
694
|
// Default: pointing up
|
|
692
695
|
this.pointer.style.transform = 'rotate(0deg)';
|
|
693
|
-
this.bubble.style.transform = 'translateY(
|
|
696
|
+
this.bubble.style.transform = 'translateY(28px)';
|
|
694
697
|
}
|
|
695
698
|
|
|
696
699
|
this.container.style.display = 'flex';
|
|
@@ -713,9 +716,17 @@ class Pointy {
|
|
|
713
716
|
|
|
714
717
|
this._startTracking();
|
|
715
718
|
|
|
716
|
-
// Show bubble immediately with fade
|
|
719
|
+
// Show bubble immediately with fade (only if content is not empty)
|
|
720
|
+
const hasContent = this.currentMessages.length > 0 &&
|
|
721
|
+
this.currentMessages.some(m => m !== '' && m !== null && m !== undefined);
|
|
722
|
+
|
|
717
723
|
this.bubble.style.transition = `opacity ${this.bubbleFadeDuration}ms ease`;
|
|
718
|
-
|
|
724
|
+
if (hasContent) {
|
|
725
|
+
this.bubble.style.opacity = '1';
|
|
726
|
+
} else {
|
|
727
|
+
this.bubble.style.opacity = '0';
|
|
728
|
+
this.bubble.style.pointerEvents = 'none';
|
|
729
|
+
}
|
|
719
730
|
|
|
720
731
|
// Re-enable transitions after bubble fade completes
|
|
721
732
|
setTimeout(() => {
|
|
@@ -744,9 +755,18 @@ class Pointy {
|
|
|
744
755
|
this._startTracking();
|
|
745
756
|
|
|
746
757
|
// Show bubble with fade after arriving at first target
|
|
758
|
+
// Show bubble with fade after arriving at first target (only if content is not empty)
|
|
747
759
|
setTimeout(() => {
|
|
760
|
+
const hasContent = this.currentMessages.length > 0 &&
|
|
761
|
+
this.currentMessages.some(m => m !== '' && m !== null && m !== undefined);
|
|
762
|
+
|
|
748
763
|
this.bubble.style.transition = '';
|
|
749
|
-
|
|
764
|
+
if (hasContent) {
|
|
765
|
+
this.bubble.style.opacity = '1';
|
|
766
|
+
} else {
|
|
767
|
+
this.bubble.style.opacity = '0';
|
|
768
|
+
this.bubble.style.pointerEvents = 'none';
|
|
769
|
+
}
|
|
750
770
|
|
|
751
771
|
// Start message cycle if multi-message
|
|
752
772
|
if (this.messageInterval && this.currentMessages.length > 1 && !this._messageIntervalId) {
|
|
@@ -998,6 +1018,48 @@ class Pointy {
|
|
|
998
1018
|
}
|
|
999
1019
|
|
|
1000
1020
|
updateContent(newContent, animate = true) {
|
|
1021
|
+
// Check if content is empty
|
|
1022
|
+
const isEmpty = newContent === '' || newContent === null || newContent === undefined ||
|
|
1023
|
+
(Array.isArray(newContent) && newContent.length === 0) ||
|
|
1024
|
+
(Array.isArray(newContent) && newContent.every(m => m === '' || m === null || m === undefined));
|
|
1025
|
+
|
|
1026
|
+
if (isEmpty) {
|
|
1027
|
+
// Hide bubble when content is empty
|
|
1028
|
+
this.bubble.style.opacity = '0';
|
|
1029
|
+
this.bubble.style.pointerEvents = 'none';
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Track if bubble was hidden (needs special handling)
|
|
1034
|
+
const wasHidden = this.bubble.style.opacity === '0';
|
|
1035
|
+
|
|
1036
|
+
// Show bubble if it was hidden - need to make visible BEFORE measuring
|
|
1037
|
+
if (wasHidden && this.isVisible) {
|
|
1038
|
+
// Temporarily disable ALL transitions for instant position update
|
|
1039
|
+
const oldBubbleTransition = this.bubble.style.transition;
|
|
1040
|
+
const oldPointerTransition = this.pointer.style.transition;
|
|
1041
|
+
this.bubble.style.transition = 'none';
|
|
1042
|
+
this.pointer.style.transition = 'none';
|
|
1043
|
+
|
|
1044
|
+
this.bubble.style.opacity = '1';
|
|
1045
|
+
this.bubble.style.pointerEvents = '';
|
|
1046
|
+
|
|
1047
|
+
// Force reflow to apply opacity
|
|
1048
|
+
this.bubble.offsetHeight;
|
|
1049
|
+
|
|
1050
|
+
// Update position with bubble visible (so offsetHeight works)
|
|
1051
|
+
this.updatePosition();
|
|
1052
|
+
|
|
1053
|
+
// Force another reflow to apply position
|
|
1054
|
+
this.bubble.offsetHeight;
|
|
1055
|
+
|
|
1056
|
+
// Re-enable transitions after a frame
|
|
1057
|
+
requestAnimationFrame(() => {
|
|
1058
|
+
this.bubble.style.transition = oldBubbleTransition;
|
|
1059
|
+
this.pointer.style.transition = oldPointerTransition;
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1001
1063
|
// Skip if content is the same (only for string content)
|
|
1002
1064
|
if (typeof newContent === 'string' && this.bubbleText.innerHTML === newContent) {
|
|
1003
1065
|
return;
|
|
@@ -1019,7 +1081,7 @@ class Pointy {
|
|
|
1019
1081
|
* @param {boolean} fromStepChange - Whether this is from a step change (internal)
|
|
1020
1082
|
* @private
|
|
1021
1083
|
*/
|
|
1022
|
-
|
|
1084
|
+
_applyMessages(content, fromStepChange = false) {
|
|
1023
1085
|
// Check if cycle was running before
|
|
1024
1086
|
const wasRunning = this._messageIntervalId !== null;
|
|
1025
1087
|
|
|
@@ -1033,8 +1095,8 @@ class Pointy {
|
|
|
1033
1095
|
// Show first message
|
|
1034
1096
|
this.updateContent(this.currentMessages[0]);
|
|
1035
1097
|
|
|
1036
|
-
// Only auto-start cycle on step changes, not on manual
|
|
1037
|
-
// For manual
|
|
1098
|
+
// Only auto-start cycle on step changes, not on manual setMessages
|
|
1099
|
+
// For manual setMessages, user must call resumeMessageCycle()
|
|
1038
1100
|
if (fromStepChange && this.messageInterval && this.currentMessages.length > 1) {
|
|
1039
1101
|
this._startMessageCycle();
|
|
1040
1102
|
} else if (wasRunning && this.currentMessages.length > 1) {
|
|
@@ -1244,16 +1306,41 @@ class Pointy {
|
|
|
1244
1306
|
}
|
|
1245
1307
|
|
|
1246
1308
|
/**
|
|
1247
|
-
* Set
|
|
1309
|
+
* Set/update the current message (at current index)
|
|
1310
|
+
* @param {string} message - New message content
|
|
1311
|
+
* @param {boolean} animate - Whether to animate the change (default: true)
|
|
1312
|
+
*/
|
|
1313
|
+
setMessage(message, animate = true) {
|
|
1314
|
+
const oldMessage = this.currentMessages[this.currentMessageIndex];
|
|
1315
|
+
this.currentMessages[this.currentMessageIndex] = message;
|
|
1316
|
+
|
|
1317
|
+
this.updateContent(message, animate);
|
|
1318
|
+
|
|
1319
|
+
// Ensure position is updated immediately for non-animated changes
|
|
1320
|
+
if (!animate) {
|
|
1321
|
+
this.updatePosition();
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
this._emit('messageUpdate', {
|
|
1325
|
+
index: this.currentMessageIndex,
|
|
1326
|
+
message: message,
|
|
1327
|
+
oldMessage: oldMessage,
|
|
1328
|
+
total: this.currentMessages.length,
|
|
1329
|
+
animated: animate
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Set messages programmatically (replaces current messages)
|
|
1248
1335
|
* @param {string|string[]} content - Single message or array of messages
|
|
1249
1336
|
* @param {boolean} animate - Whether to animate the change (default: true)
|
|
1250
1337
|
*/
|
|
1251
|
-
|
|
1338
|
+
setMessages(content, animate = true) {
|
|
1252
1339
|
// Check if cycle was running before
|
|
1253
1340
|
const wasRunning = this._messageIntervalId !== null;
|
|
1254
1341
|
|
|
1255
1342
|
if (animate) {
|
|
1256
|
-
this.
|
|
1343
|
+
this._applyMessages(content, false); // false = not from step change
|
|
1257
1344
|
} else {
|
|
1258
1345
|
// Stop any existing auto-cycle
|
|
1259
1346
|
this._stopMessageCycle();
|
|
@@ -1272,7 +1359,7 @@ class Pointy {
|
|
|
1272
1359
|
}
|
|
1273
1360
|
}
|
|
1274
1361
|
|
|
1275
|
-
this._emit('
|
|
1362
|
+
this._emit('messagesSet', {
|
|
1276
1363
|
messages: this.currentMessages,
|
|
1277
1364
|
total: this.currentMessages.length,
|
|
1278
1365
|
animated: animate,
|
|
@@ -1631,6 +1718,10 @@ class Pointy {
|
|
|
1631
1718
|
// Set direction: step.direction can be 'up', 'down', or undefined (auto)
|
|
1632
1719
|
this.manualDirection = step.direction || null;
|
|
1633
1720
|
|
|
1721
|
+
// Reset velocity tracking for new target
|
|
1722
|
+
this._targetYHistory = [];
|
|
1723
|
+
this.lastTargetY = null;
|
|
1724
|
+
|
|
1634
1725
|
// Pause floating animation during movement
|
|
1635
1726
|
this.container.classList.add(this.classNames.moving);
|
|
1636
1727
|
if (this.moveTimeout) clearTimeout(this.moveTimeout);
|
|
@@ -1658,7 +1749,7 @@ class Pointy {
|
|
|
1658
1749
|
|
|
1659
1750
|
this._emit('move', { index: index, step: step });
|
|
1660
1751
|
|
|
1661
|
-
this.
|
|
1752
|
+
this._applyMessages(step.content, true); // true = from step change, auto-start cycle
|
|
1662
1753
|
this.targetElement = Pointy.getTargetElement(step.target);
|
|
1663
1754
|
this.updatePosition();
|
|
1664
1755
|
|
|
@@ -1951,6 +2042,10 @@ class Pointy {
|
|
|
1951
2042
|
// Set manual direction (null means auto)
|
|
1952
2043
|
this.manualDirection = direction || null;
|
|
1953
2044
|
|
|
2045
|
+
// Reset velocity tracking for new target
|
|
2046
|
+
this._targetYHistory = [];
|
|
2047
|
+
this.lastTargetY = null;
|
|
2048
|
+
|
|
1954
2049
|
// Pause floating animation during movement
|
|
1955
2050
|
this.container.classList.add(this.classNames.moving);
|
|
1956
2051
|
if (this.moveTimeout) clearTimeout(this.moveTimeout);
|
|
@@ -1967,7 +2062,7 @@ class Pointy {
|
|
|
1967
2062
|
this.targetElement = toTarget;
|
|
1968
2063
|
|
|
1969
2064
|
if (content !== undefined) {
|
|
1970
|
-
this.
|
|
2065
|
+
this._applyMessages(content, false); // false = not from step change, don't auto-start cycle
|
|
1971
2066
|
} else {
|
|
1972
2067
|
// No new content - keep cycling if it was running
|
|
1973
2068
|
// (cycle state is preserved)
|
|
@@ -2036,7 +2131,7 @@ class Pointy {
|
|
|
2036
2131
|
* Content:
|
|
2037
2132
|
* - messagesSet: When messages array is set for a step
|
|
2038
2133
|
* - messageChange: When current message changes (next/prev message) - includes isAuto flag
|
|
2039
|
-
* -
|
|
2134
|
+
* - messagesSet: When setMessages() is called
|
|
2040
2135
|
* - messageCycleStart: When auto message cycling starts
|
|
2041
2136
|
* - messageCycleStop: When auto message cycling stops
|
|
2042
2137
|
* - messageCyclePause: When message cycling is paused
|