@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 CHANGED
@@ -137,8 +137,10 @@ pointy.reset(); // Reset to initial position
137
137
  ### Content
138
138
 
139
139
  ```javascript
140
- pointy.setContent('New message');
141
- pointy.setContent(['Message 1', 'Message 2']);
140
+ pointy.setMessages('New message'); // Replace all messages
141
+ pointy.setMessages(['Message 1', 'Message 2']); // Set multiple messages
142
+
143
+ pointy.setMessage('Updated text'); // Update current message only
142
144
 
143
145
  pointy.nextMessage();
144
146
  pointy.prevMessage();
@@ -171,6 +173,44 @@ pointy.pauseMessageCycle();
171
173
  pointy.resumeMessageCycle();
172
174
  ```
173
175
 
176
+ ### Setters
177
+
178
+ All setters emit corresponding `*Change` events:
179
+
180
+ ```javascript
181
+ // Animation
182
+ pointy.setEasing('bounce');
183
+ pointy.setAnimationDuration(800);
184
+ pointy.setIntroFadeDuration(500);
185
+ pointy.setBubbleFadeDuration(300);
186
+ pointy.setMessageTransitionDuration(400);
187
+ pointy.setFloatingAnimation(true);
188
+
189
+ // Position
190
+ pointy.setOffset(30, 20);
191
+ pointy.setInitialPosition('top-left');
192
+ pointy.setInitialPositionOffset(50);
193
+
194
+ // Tracking
195
+ pointy.setTracking(true);
196
+ pointy.setTrackingFps(30);
197
+
198
+ // Messages
199
+ pointy.setMessageInterval(2000);
200
+
201
+ // Autoplay
202
+ pointy.setAutoplayInterval(3000);
203
+ pointy.setAutoplayWaitForMessages(true);
204
+
205
+ // Completion
206
+ pointy.setResetOnComplete(true);
207
+ pointy.setHideOnComplete(true);
208
+ pointy.setHideOnCompleteDelay(500);
209
+
210
+ // Styling
211
+ pointy.setPointerSvg('<svg>...</svg>');
212
+ ```
213
+
174
214
  ## Easing Presets
175
215
 
176
216
  ```javascript
@@ -259,8 +299,8 @@ pointy.on('all', (data) => {
259
299
  #### Content
260
300
  | Event | Data |
261
301
  |-------|------|
262
- | `contentSet` | `{ messages, total, animated, cyclePaused }` |
263
- | `messagesSet` | `{ messages, total, cyclePaused }` |
302
+ | `messagesSet` | `{ messages, total, animated, cyclePaused }` |
303
+ | `messageUpdate` | `{ index, message, oldMessage, total, animated }` |
264
304
  | `messageChange` | `{ fromIndex, toIndex, message, total, isAuto? }` |
265
305
 
266
306
  #### Message Cycle
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Pointy - A lightweight tooltip library with animated pointer
3
- * @version 1.0.1
3
+ * @version 1.0.3
4
4
  * @license MIT
5
5
  */
6
6
  /**
@@ -24,6 +24,7 @@
24
24
  * @options
25
25
  * - steps {Array<{target, content, direction?, duration?}>} - Tour steps
26
26
  * - target {string|HTMLElement} - Initial target element
27
+ * - content {string|string[]} - Initial content/messages (single-step use)
27
28
  * - offsetX {number} - Horizontal offset from target (default: 20)
28
29
  * - offsetY {number} - Vertical offset from target (default: 16)
29
30
  * - trackingFps {number} - Position update FPS, 0 = unlimited (default: 60)
@@ -78,9 +79,9 @@
78
79
  * - introAnimationEnd: Initial fade-in animation completed
79
80
  *
80
81
  * Content:
81
- * - contentSet: Content updated via setContent()
82
- * - messagesSet: New messages array set for step
83
- * - messageChange: Message changed (manual or auto)
82
+ * - messagesSet: Messages array replaced via setMessages()
83
+ * - messageUpdate: Single message updated via setMessage()
84
+ * - messageChange: Message changed (navigation or auto-cycle)
84
85
  *
85
86
  * Message Cycle:
86
87
  * - messageCycleStart: Auto message cycling started
@@ -131,7 +132,7 @@
131
132
  * Core: show(), hide(), destroy()
132
133
  * Navigation: next(), prev(), goToStep(index), reset(), restart()
133
134
  * Custom Target: pointTo(target, content?, direction?)
134
- * Content: setContent(content), nextMessage(), prevMessage(), goToMessage(index)
135
+ * Content: setMessages(content), setMessage(msg), nextMessage(), prevMessage(), goToMessage(index)
135
136
  * Message Cycle: startMessageCycle(interval?), stopMessageCycle(), pauseMessageCycle(), resumeMessageCycle()
136
137
  * Autoplay: startAutoplay(), stopAutoplay(), pauseAutoplay(), resumeAutoplay()
137
138
  * Animation: animateToInitialPosition()
@@ -506,6 +507,8 @@ class Pointy {
506
507
 
507
508
  this.bubble = document.createElement('div');
508
509
  this.bubble.className = this.classNames.bubble;
510
+ // Set initial bubble position for pointing up (default)
511
+ this.bubble.style.transform = 'translateY(28px)';
509
512
 
510
513
  this.bubbleText = document.createElement('span');
511
514
  this.bubbleText.className = this.classNames.bubbleText;
@@ -695,7 +698,7 @@ class Pointy {
695
698
  } else {
696
699
  // Default: pointing up
697
700
  this.pointer.style.transform = 'rotate(0deg)';
698
- this.bubble.style.transform = 'translateY(0)';
701
+ this.bubble.style.transform = 'translateY(28px)';
699
702
  }
700
703
 
701
704
  this.container.style.display = 'flex';
@@ -718,9 +721,17 @@ class Pointy {
718
721
 
719
722
  this._startTracking();
720
723
 
721
- // Show bubble immediately with fade
724
+ // Show bubble immediately with fade (only if content is not empty)
725
+ const hasContent = this.currentMessages.length > 0 &&
726
+ this.currentMessages.some(m => m !== '' && m !== null && m !== undefined);
727
+
722
728
  this.bubble.style.transition = `opacity ${this.bubbleFadeDuration}ms ease`;
723
- this.bubble.style.opacity = '1';
729
+ if (hasContent) {
730
+ this.bubble.style.opacity = '1';
731
+ } else {
732
+ this.bubble.style.opacity = '0';
733
+ this.bubble.style.pointerEvents = 'none';
734
+ }
724
735
 
725
736
  // Re-enable transitions after bubble fade completes
726
737
  setTimeout(() => {
@@ -749,9 +760,18 @@ class Pointy {
749
760
  this._startTracking();
750
761
 
751
762
  // Show bubble with fade after arriving at first target
763
+ // Show bubble with fade after arriving at first target (only if content is not empty)
752
764
  setTimeout(() => {
765
+ const hasContent = this.currentMessages.length > 0 &&
766
+ this.currentMessages.some(m => m !== '' && m !== null && m !== undefined);
767
+
753
768
  this.bubble.style.transition = '';
754
- this.bubble.style.opacity = '1';
769
+ if (hasContent) {
770
+ this.bubble.style.opacity = '1';
771
+ } else {
772
+ this.bubble.style.opacity = '0';
773
+ this.bubble.style.pointerEvents = 'none';
774
+ }
755
775
 
756
776
  // Start message cycle if multi-message
757
777
  if (this.messageInterval && this.currentMessages.length > 1 && !this._messageIntervalId) {
@@ -1003,6 +1023,48 @@ class Pointy {
1003
1023
  }
1004
1024
 
1005
1025
  updateContent(newContent, animate = true) {
1026
+ // Check if content is empty
1027
+ const isEmpty = newContent === '' || newContent === null || newContent === undefined ||
1028
+ (Array.isArray(newContent) && newContent.length === 0) ||
1029
+ (Array.isArray(newContent) && newContent.every(m => m === '' || m === null || m === undefined));
1030
+
1031
+ if (isEmpty) {
1032
+ // Hide bubble when content is empty
1033
+ this.bubble.style.opacity = '0';
1034
+ this.bubble.style.pointerEvents = 'none';
1035
+ return;
1036
+ }
1037
+
1038
+ // Track if bubble was hidden (needs special handling)
1039
+ const wasHidden = this.bubble.style.opacity === '0';
1040
+
1041
+ // Show bubble if it was hidden - need to make visible BEFORE measuring
1042
+ if (wasHidden && this.isVisible) {
1043
+ // Temporarily disable ALL transitions for instant position update
1044
+ const oldBubbleTransition = this.bubble.style.transition;
1045
+ const oldPointerTransition = this.pointer.style.transition;
1046
+ this.bubble.style.transition = 'none';
1047
+ this.pointer.style.transition = 'none';
1048
+
1049
+ this.bubble.style.opacity = '1';
1050
+ this.bubble.style.pointerEvents = '';
1051
+
1052
+ // Force reflow to apply opacity
1053
+ this.bubble.offsetHeight;
1054
+
1055
+ // Update position with bubble visible (so offsetHeight works)
1056
+ this.updatePosition();
1057
+
1058
+ // Force another reflow to apply position
1059
+ this.bubble.offsetHeight;
1060
+
1061
+ // Re-enable transitions after a frame
1062
+ requestAnimationFrame(() => {
1063
+ this.bubble.style.transition = oldBubbleTransition;
1064
+ this.pointer.style.transition = oldPointerTransition;
1065
+ });
1066
+ }
1067
+
1006
1068
  // Skip if content is the same (only for string content)
1007
1069
  if (typeof newContent === 'string' && this.bubbleText.innerHTML === newContent) {
1008
1070
  return;
@@ -1024,7 +1086,7 @@ class Pointy {
1024
1086
  * @param {boolean} fromStepChange - Whether this is from a step change (internal)
1025
1087
  * @private
1026
1088
  */
1027
- _setMessages(content, fromStepChange = false) {
1089
+ _applyMessages(content, fromStepChange = false) {
1028
1090
  // Check if cycle was running before
1029
1091
  const wasRunning = this._messageIntervalId !== null;
1030
1092
 
@@ -1038,8 +1100,8 @@ class Pointy {
1038
1100
  // Show first message
1039
1101
  this.updateContent(this.currentMessages[0]);
1040
1102
 
1041
- // Only auto-start cycle on step changes, not on manual setContent
1042
- // For manual setContent, user must call resumeMessageCycle()
1103
+ // Only auto-start cycle on step changes, not on manual setMessages
1104
+ // For manual setMessages, user must call resumeMessageCycle()
1043
1105
  if (fromStepChange && this.messageInterval && this.currentMessages.length > 1) {
1044
1106
  this._startMessageCycle();
1045
1107
  } else if (wasRunning && this.currentMessages.length > 1) {
@@ -1249,16 +1311,41 @@ class Pointy {
1249
1311
  }
1250
1312
 
1251
1313
  /**
1252
- * Set content programmatically (replaces current messages)
1314
+ * Set/update the current message (at current index)
1315
+ * @param {string} message - New message content
1316
+ * @param {boolean} animate - Whether to animate the change (default: true)
1317
+ */
1318
+ setMessage(message, animate = true) {
1319
+ const oldMessage = this.currentMessages[this.currentMessageIndex];
1320
+ this.currentMessages[this.currentMessageIndex] = message;
1321
+
1322
+ this.updateContent(message, animate);
1323
+
1324
+ // Ensure position is updated immediately for non-animated changes
1325
+ if (!animate) {
1326
+ this.updatePosition();
1327
+ }
1328
+
1329
+ this._emit('messageUpdate', {
1330
+ index: this.currentMessageIndex,
1331
+ message: message,
1332
+ oldMessage: oldMessage,
1333
+ total: this.currentMessages.length,
1334
+ animated: animate
1335
+ });
1336
+ }
1337
+
1338
+ /**
1339
+ * Set messages programmatically (replaces current messages)
1253
1340
  * @param {string|string[]} content - Single message or array of messages
1254
1341
  * @param {boolean} animate - Whether to animate the change (default: true)
1255
1342
  */
1256
- setContent(content, animate = true) {
1343
+ setMessages(content, animate = true) {
1257
1344
  // Check if cycle was running before
1258
1345
  const wasRunning = this._messageIntervalId !== null;
1259
1346
 
1260
1347
  if (animate) {
1261
- this._setMessages(content, false); // false = not from step change
1348
+ this._applyMessages(content, false); // false = not from step change
1262
1349
  } else {
1263
1350
  // Stop any existing auto-cycle
1264
1351
  this._stopMessageCycle();
@@ -1277,7 +1364,7 @@ class Pointy {
1277
1364
  }
1278
1365
  }
1279
1366
 
1280
- this._emit('contentSet', {
1367
+ this._emit('messagesSet', {
1281
1368
  messages: this.currentMessages,
1282
1369
  total: this.currentMessages.length,
1283
1370
  animated: animate,
@@ -1636,6 +1723,10 @@ class Pointy {
1636
1723
  // Set direction: step.direction can be 'up', 'down', or undefined (auto)
1637
1724
  this.manualDirection = step.direction || null;
1638
1725
 
1726
+ // Reset velocity tracking for new target
1727
+ this._targetYHistory = [];
1728
+ this.lastTargetY = null;
1729
+
1639
1730
  // Pause floating animation during movement
1640
1731
  this.container.classList.add(this.classNames.moving);
1641
1732
  if (this.moveTimeout) clearTimeout(this.moveTimeout);
@@ -1663,7 +1754,7 @@ class Pointy {
1663
1754
 
1664
1755
  this._emit('move', { index: index, step: step });
1665
1756
 
1666
- this._setMessages(step.content, true); // true = from step change, auto-start cycle
1757
+ this._applyMessages(step.content, true); // true = from step change, auto-start cycle
1667
1758
  this.targetElement = Pointy.getTargetElement(step.target);
1668
1759
  this.updatePosition();
1669
1760
 
@@ -1956,6 +2047,10 @@ class Pointy {
1956
2047
  // Set manual direction (null means auto)
1957
2048
  this.manualDirection = direction || null;
1958
2049
 
2050
+ // Reset velocity tracking for new target
2051
+ this._targetYHistory = [];
2052
+ this.lastTargetY = null;
2053
+
1959
2054
  // Pause floating animation during movement
1960
2055
  this.container.classList.add(this.classNames.moving);
1961
2056
  if (this.moveTimeout) clearTimeout(this.moveTimeout);
@@ -1972,7 +2067,7 @@ class Pointy {
1972
2067
  this.targetElement = toTarget;
1973
2068
 
1974
2069
  if (content !== undefined) {
1975
- this._setMessages(content, false); // false = not from step change, don't auto-start cycle
2070
+ this._applyMessages(content, false); // false = not from step change, don't auto-start cycle
1976
2071
  }
1977
2072
 
1978
2073
  this.updatePosition();
@@ -2038,7 +2133,7 @@ class Pointy {
2038
2133
  * Content:
2039
2134
  * - messagesSet: When messages array is set for a step
2040
2135
  * - messageChange: When current message changes (next/prev message) - includes isAuto flag
2041
- * - contentSet: When setContent() is called
2136
+ * - messagesSet: When setMessages() is called
2042
2137
  * - messageCycleStart: When auto message cycling starts
2043
2138
  * - messageCycleStop: When auto message cycling stops
2044
2139
  * - messageCyclePause: When message cycling is paused
package/dist/pointy.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Pointy - A lightweight tooltip library with animated pointer
3
- * @version 1.0.1
3
+ * @version 1.0.3
4
4
  * @license MIT
5
5
  */
6
6
  (function (global, factory) {
@@ -30,6 +30,7 @@
30
30
  * @options
31
31
  * - steps {Array<{target, content, direction?, duration?}>} - Tour steps
32
32
  * - target {string|HTMLElement} - Initial target element
33
+ * - content {string|string[]} - Initial content/messages (single-step use)
33
34
  * - offsetX {number} - Horizontal offset from target (default: 20)
34
35
  * - offsetY {number} - Vertical offset from target (default: 16)
35
36
  * - trackingFps {number} - Position update FPS, 0 = unlimited (default: 60)
@@ -84,9 +85,9 @@
84
85
  * - introAnimationEnd: Initial fade-in animation completed
85
86
  *
86
87
  * Content:
87
- * - contentSet: Content updated via setContent()
88
- * - messagesSet: New messages array set for step
89
- * - messageChange: Message changed (manual or auto)
88
+ * - messagesSet: Messages array replaced via setMessages()
89
+ * - messageUpdate: Single message updated via setMessage()
90
+ * - messageChange: Message changed (navigation or auto-cycle)
90
91
  *
91
92
  * Message Cycle:
92
93
  * - messageCycleStart: Auto message cycling started
@@ -137,7 +138,7 @@
137
138
  * Core: show(), hide(), destroy()
138
139
  * Navigation: next(), prev(), goToStep(index), reset(), restart()
139
140
  * Custom Target: pointTo(target, content?, direction?)
140
- * Content: setContent(content), nextMessage(), prevMessage(), goToMessage(index)
141
+ * Content: setMessages(content), setMessage(msg), nextMessage(), prevMessage(), goToMessage(index)
141
142
  * Message Cycle: startMessageCycle(interval?), stopMessageCycle(), pauseMessageCycle(), resumeMessageCycle()
142
143
  * Autoplay: startAutoplay(), stopAutoplay(), pauseAutoplay(), resumeAutoplay()
143
144
  * Animation: animateToInitialPosition()
@@ -512,6 +513,8 @@
512
513
 
513
514
  this.bubble = document.createElement('div');
514
515
  this.bubble.className = this.classNames.bubble;
516
+ // Set initial bubble position for pointing up (default)
517
+ this.bubble.style.transform = 'translateY(28px)';
515
518
 
516
519
  this.bubbleText = document.createElement('span');
517
520
  this.bubbleText.className = this.classNames.bubbleText;
@@ -701,7 +704,7 @@
701
704
  } else {
702
705
  // Default: pointing up
703
706
  this.pointer.style.transform = 'rotate(0deg)';
704
- this.bubble.style.transform = 'translateY(0)';
707
+ this.bubble.style.transform = 'translateY(28px)';
705
708
  }
706
709
 
707
710
  this.container.style.display = 'flex';
@@ -724,9 +727,17 @@
724
727
 
725
728
  this._startTracking();
726
729
 
727
- // Show bubble immediately with fade
730
+ // Show bubble immediately with fade (only if content is not empty)
731
+ const hasContent = this.currentMessages.length > 0 &&
732
+ this.currentMessages.some(m => m !== '' && m !== null && m !== undefined);
733
+
728
734
  this.bubble.style.transition = `opacity ${this.bubbleFadeDuration}ms ease`;
729
- this.bubble.style.opacity = '1';
735
+ if (hasContent) {
736
+ this.bubble.style.opacity = '1';
737
+ } else {
738
+ this.bubble.style.opacity = '0';
739
+ this.bubble.style.pointerEvents = 'none';
740
+ }
730
741
 
731
742
  // Re-enable transitions after bubble fade completes
732
743
  setTimeout(() => {
@@ -755,9 +766,18 @@
755
766
  this._startTracking();
756
767
 
757
768
  // Show bubble with fade after arriving at first target
769
+ // Show bubble with fade after arriving at first target (only if content is not empty)
758
770
  setTimeout(() => {
771
+ const hasContent = this.currentMessages.length > 0 &&
772
+ this.currentMessages.some(m => m !== '' && m !== null && m !== undefined);
773
+
759
774
  this.bubble.style.transition = '';
760
- this.bubble.style.opacity = '1';
775
+ if (hasContent) {
776
+ this.bubble.style.opacity = '1';
777
+ } else {
778
+ this.bubble.style.opacity = '0';
779
+ this.bubble.style.pointerEvents = 'none';
780
+ }
761
781
 
762
782
  // Start message cycle if multi-message
763
783
  if (this.messageInterval && this.currentMessages.length > 1 && !this._messageIntervalId) {
@@ -1009,6 +1029,48 @@
1009
1029
  }
1010
1030
 
1011
1031
  updateContent(newContent, animate = true) {
1032
+ // Check if content is empty
1033
+ const isEmpty = newContent === '' || newContent === null || newContent === undefined ||
1034
+ (Array.isArray(newContent) && newContent.length === 0) ||
1035
+ (Array.isArray(newContent) && newContent.every(m => m === '' || m === null || m === undefined));
1036
+
1037
+ if (isEmpty) {
1038
+ // Hide bubble when content is empty
1039
+ this.bubble.style.opacity = '0';
1040
+ this.bubble.style.pointerEvents = 'none';
1041
+ return;
1042
+ }
1043
+
1044
+ // Track if bubble was hidden (needs special handling)
1045
+ const wasHidden = this.bubble.style.opacity === '0';
1046
+
1047
+ // Show bubble if it was hidden - need to make visible BEFORE measuring
1048
+ if (wasHidden && this.isVisible) {
1049
+ // Temporarily disable ALL transitions for instant position update
1050
+ const oldBubbleTransition = this.bubble.style.transition;
1051
+ const oldPointerTransition = this.pointer.style.transition;
1052
+ this.bubble.style.transition = 'none';
1053
+ this.pointer.style.transition = 'none';
1054
+
1055
+ this.bubble.style.opacity = '1';
1056
+ this.bubble.style.pointerEvents = '';
1057
+
1058
+ // Force reflow to apply opacity
1059
+ this.bubble.offsetHeight;
1060
+
1061
+ // Update position with bubble visible (so offsetHeight works)
1062
+ this.updatePosition();
1063
+
1064
+ // Force another reflow to apply position
1065
+ this.bubble.offsetHeight;
1066
+
1067
+ // Re-enable transitions after a frame
1068
+ requestAnimationFrame(() => {
1069
+ this.bubble.style.transition = oldBubbleTransition;
1070
+ this.pointer.style.transition = oldPointerTransition;
1071
+ });
1072
+ }
1073
+
1012
1074
  // Skip if content is the same (only for string content)
1013
1075
  if (typeof newContent === 'string' && this.bubbleText.innerHTML === newContent) {
1014
1076
  return;
@@ -1030,7 +1092,7 @@
1030
1092
  * @param {boolean} fromStepChange - Whether this is from a step change (internal)
1031
1093
  * @private
1032
1094
  */
1033
- _setMessages(content, fromStepChange = false) {
1095
+ _applyMessages(content, fromStepChange = false) {
1034
1096
  // Check if cycle was running before
1035
1097
  const wasRunning = this._messageIntervalId !== null;
1036
1098
 
@@ -1044,8 +1106,8 @@
1044
1106
  // Show first message
1045
1107
  this.updateContent(this.currentMessages[0]);
1046
1108
 
1047
- // Only auto-start cycle on step changes, not on manual setContent
1048
- // For manual setContent, user must call resumeMessageCycle()
1109
+ // Only auto-start cycle on step changes, not on manual setMessages
1110
+ // For manual setMessages, user must call resumeMessageCycle()
1049
1111
  if (fromStepChange && this.messageInterval && this.currentMessages.length > 1) {
1050
1112
  this._startMessageCycle();
1051
1113
  } else if (wasRunning && this.currentMessages.length > 1) {
@@ -1255,16 +1317,41 @@
1255
1317
  }
1256
1318
 
1257
1319
  /**
1258
- * Set content programmatically (replaces current messages)
1320
+ * Set/update the current message (at current index)
1321
+ * @param {string} message - New message content
1322
+ * @param {boolean} animate - Whether to animate the change (default: true)
1323
+ */
1324
+ setMessage(message, animate = true) {
1325
+ const oldMessage = this.currentMessages[this.currentMessageIndex];
1326
+ this.currentMessages[this.currentMessageIndex] = message;
1327
+
1328
+ this.updateContent(message, animate);
1329
+
1330
+ // Ensure position is updated immediately for non-animated changes
1331
+ if (!animate) {
1332
+ this.updatePosition();
1333
+ }
1334
+
1335
+ this._emit('messageUpdate', {
1336
+ index: this.currentMessageIndex,
1337
+ message: message,
1338
+ oldMessage: oldMessage,
1339
+ total: this.currentMessages.length,
1340
+ animated: animate
1341
+ });
1342
+ }
1343
+
1344
+ /**
1345
+ * Set messages programmatically (replaces current messages)
1259
1346
  * @param {string|string[]} content - Single message or array of messages
1260
1347
  * @param {boolean} animate - Whether to animate the change (default: true)
1261
1348
  */
1262
- setContent(content, animate = true) {
1349
+ setMessages(content, animate = true) {
1263
1350
  // Check if cycle was running before
1264
1351
  const wasRunning = this._messageIntervalId !== null;
1265
1352
 
1266
1353
  if (animate) {
1267
- this._setMessages(content, false); // false = not from step change
1354
+ this._applyMessages(content, false); // false = not from step change
1268
1355
  } else {
1269
1356
  // Stop any existing auto-cycle
1270
1357
  this._stopMessageCycle();
@@ -1283,7 +1370,7 @@
1283
1370
  }
1284
1371
  }
1285
1372
 
1286
- this._emit('contentSet', {
1373
+ this._emit('messagesSet', {
1287
1374
  messages: this.currentMessages,
1288
1375
  total: this.currentMessages.length,
1289
1376
  animated: animate,
@@ -1642,6 +1729,10 @@
1642
1729
  // Set direction: step.direction can be 'up', 'down', or undefined (auto)
1643
1730
  this.manualDirection = step.direction || null;
1644
1731
 
1732
+ // Reset velocity tracking for new target
1733
+ this._targetYHistory = [];
1734
+ this.lastTargetY = null;
1735
+
1645
1736
  // Pause floating animation during movement
1646
1737
  this.container.classList.add(this.classNames.moving);
1647
1738
  if (this.moveTimeout) clearTimeout(this.moveTimeout);
@@ -1669,7 +1760,7 @@
1669
1760
 
1670
1761
  this._emit('move', { index: index, step: step });
1671
1762
 
1672
- this._setMessages(step.content, true); // true = from step change, auto-start cycle
1763
+ this._applyMessages(step.content, true); // true = from step change, auto-start cycle
1673
1764
  this.targetElement = Pointy.getTargetElement(step.target);
1674
1765
  this.updatePosition();
1675
1766
 
@@ -1962,6 +2053,10 @@
1962
2053
  // Set manual direction (null means auto)
1963
2054
  this.manualDirection = direction || null;
1964
2055
 
2056
+ // Reset velocity tracking for new target
2057
+ this._targetYHistory = [];
2058
+ this.lastTargetY = null;
2059
+
1965
2060
  // Pause floating animation during movement
1966
2061
  this.container.classList.add(this.classNames.moving);
1967
2062
  if (this.moveTimeout) clearTimeout(this.moveTimeout);
@@ -1978,7 +2073,7 @@
1978
2073
  this.targetElement = toTarget;
1979
2074
 
1980
2075
  if (content !== undefined) {
1981
- this._setMessages(content, false); // false = not from step change, don't auto-start cycle
2076
+ this._applyMessages(content, false); // false = not from step change, don't auto-start cycle
1982
2077
  }
1983
2078
 
1984
2079
  this.updatePosition();
@@ -2044,7 +2139,7 @@
2044
2139
  * Content:
2045
2140
  * - messagesSet: When messages array is set for a step
2046
2141
  * - messageChange: When current message changes (next/prev message) - includes isAuto flag
2047
- * - contentSet: When setContent() is called
2142
+ * - messagesSet: When setMessages() is called
2048
2143
  * - messageCycleStart: When auto message cycling starts
2049
2144
  * - messageCycleStop: When auto message cycling stops
2050
2145
  * - messageCyclePause: When message cycling is paused