releasebird-javascript-sdk 1.0.73 → 1.0.76

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.
@@ -1,5 +1,6 @@
1
1
  import RbirdUtils from "./RbirdUtils";
2
2
  import ReleasebirdImageViewer from "./ReleasebirdImageViewer";
3
+ import ReleasebirdFormViewer from "./ReleasebirdFormViewer";
3
4
  import {
4
5
  hideWidgetImage,
5
6
  launcher0,
@@ -44,6 +45,15 @@ export default class RbirdWebsiteWidget {
44
45
  messageBubblesContainer = null;
45
46
  dismissedBubbles = new Set(); // Track dismissed bubbles by chatId
46
47
 
48
+ // Drag functionality
49
+ widgetWrapper = null;
50
+ dragHandle = null;
51
+ isDragging = false;
52
+ dragStartX = 0;
53
+ dragStartY = 0;
54
+ initialX = 0;
55
+ initialY = 0;
56
+
47
57
  static getInstance() {
48
58
  if (!this.instance) {
49
59
  this.instance = new RbirdWebsiteWidget();
@@ -72,11 +82,23 @@ export default class RbirdWebsiteWidget {
72
82
  this.websiteWidgetVisible = true;
73
83
 
74
84
  try {
85
+ // Create wrapper for drag handle and button
86
+ this.widgetWrapper = document.createElement("div");
87
+ this.widgetWrapper.className = "rbird-widget-wrapper";
88
+ document.body.appendChild(this.widgetWrapper);
89
+
90
+ // Create drag handle
91
+ this.dragHandle = this.createDragHandle();
92
+ this.widgetWrapper.appendChild(this.dragHandle);
93
+
94
+ // Create launcher button
75
95
  let elem = document.createElement("div");
76
- elem.onclick = () => {
77
- this.openWebsiteWidget();
96
+ elem.onclick = (e) => {
97
+ if (!this.isDragging) {
98
+ this.openWebsiteWidget();
99
+ }
78
100
  };
79
- document.body.appendChild(elem);
101
+ this.widgetWrapper.appendChild(elem);
80
102
  this.websiteWidget = elem;
81
103
 
82
104
  if (RbirdSessionManager.getInstance().widgetSettings?.allowClose && !this.noButton) {
@@ -94,6 +116,7 @@ export default class RbirdWebsiteWidget {
94
116
  document.body.appendChild(this.countBadge);
95
117
 
96
118
  this.styleWidget();
119
+ this.initDragListeners();
97
120
 
98
121
  resolve();
99
122
  } catch (error) {
@@ -108,6 +131,10 @@ export default class RbirdWebsiteWidget {
108
131
 
109
132
  openWebsiteWidget() {
110
133
  if (typeof window === 'undefined' || typeof document === 'undefined') return;
134
+
135
+ // Position the modal relative to the bubble position
136
+ this.positionWidgetContent();
137
+
111
138
  RbirdUtils.addClass(this.widgetContent, "cta__modal--visible");
112
139
  RbirdUtils.addClass(this.backdrop, "cta__modal-dimmer--visible");
113
140
 
@@ -127,6 +154,9 @@ export default class RbirdWebsiteWidget {
127
154
  this.hideWidgetButton.style.display = 'none';
128
155
  }
129
156
  this.websiteWidget.style.display = 'none';
157
+ if (this.widgetWrapper) {
158
+ this.widgetWrapper.style.display = 'none';
159
+ }
130
160
  }
131
161
 
132
162
  if (RbirdSessionManager.getInstance().widgetSettings.launcher === 5) {
@@ -164,6 +194,9 @@ export default class RbirdWebsiteWidget {
164
194
  if (this.hideWidgetButton) {
165
195
  this.hideWidgetButton.style.display = 'none';
166
196
  this.websiteWidget.style.display = 'none';
197
+ if (this.widgetWrapper) {
198
+ this.widgetWrapper.style.display = 'none';
199
+ }
167
200
  }
168
201
  }
169
202
 
@@ -236,11 +269,17 @@ export default class RbirdWebsiteWidget {
236
269
  showButton(showButton) {
237
270
  if (typeof window === 'undefined') return;
238
271
  if (showButton && !this.noButton) {
272
+ if (this.widgetWrapper) {
273
+ this.widgetWrapper.style.display = 'flex';
274
+ }
239
275
  this.websiteWidget.style.display = 'block';
240
276
  if (this.hideWidgetButton && !this.noButton) {
241
277
  this.hideWidgetButton.style.display = "block";
242
278
  }
243
279
  } else {
280
+ if (this.widgetWrapper) {
281
+ this.widgetWrapper.style.display = 'none';
282
+ }
244
283
  this.websiteWidget.style.display = 'none';
245
284
  if (this.hideWidgetButton) {
246
285
  this.hideWidgetButton.style.display = "none";
@@ -267,11 +306,19 @@ export default class RbirdWebsiteWidget {
267
306
  this.initButton();
268
307
  if (!this.noButton) {
269
308
  this.websiteWidget.style.display = 'block';
309
+ if (this.widgetWrapper) {
310
+ this.widgetWrapper.style.display = 'flex';
311
+ }
270
312
  }
271
313
 
272
314
  this.websiteWidget.onclick = () => this.openWebsiteWidget();
273
315
  this.unregisterListeners();
274
316
 
317
+ // Update positions of related elements after a short delay to ensure DOM is updated
318
+ setTimeout(() => {
319
+ this.updateRelatedElementsPosition();
320
+ }, 50);
321
+
275
322
  // Reload message bubbles when widget is closed
276
323
  this.dismissedBubbles.clear(); // Reset dismissed bubbles
277
324
  this.fetchUnreadMessages();
@@ -310,6 +357,16 @@ export default class RbirdWebsiteWidget {
310
357
  ReleasebirdImageViewer.showImage(e.data?.url);
311
358
  }
312
359
 
360
+ if (e.data?.key === 'showForm') {
361
+ ReleasebirdFormViewer.showForm(
362
+ e.data?.form,
363
+ e.data?.formId,
364
+ e.data?.chatId,
365
+ e.data?.apiKey,
366
+ that.iframe
367
+ );
368
+ }
369
+
313
370
  if (e.data === 'newMessageArrived') {
314
371
  this.countNotifications();
315
372
  // Also refresh message bubbles when new message arrives
@@ -337,6 +394,29 @@ export default class RbirdWebsiteWidget {
337
394
  this.websiteWidget.className=RbirdSessionManager.getInstance().widgetSettings.launcher === 5 ? 'launcherButton5' : 'launcherButton' ;
338
395
  this.initButton();
339
396
 
397
+ // Position the wrapper based on launcher settings or saved position
398
+ const settings = RbirdSessionManager.getInstance().widgetSettings;
399
+ const launcherPosition = settings.launcherPosition || 'right';
400
+ const spaceLeftRight = settings.spaceLeftRight || 20;
401
+ const spaceBottom = settings.spaceBottom || 20;
402
+
403
+ // Set initial wrapper position
404
+ this.widgetWrapper.style.bottom = `${spaceBottom}px`;
405
+ if (launcherPosition === 'right') {
406
+ this.widgetWrapper.style.right = `${spaceLeftRight}px`;
407
+ this.widgetWrapper.style.left = 'unset';
408
+ this.widgetWrapper.style.flexDirection = 'row';
409
+ } else {
410
+ this.widgetWrapper.style.left = `${spaceLeftRight}px`;
411
+ this.widgetWrapper.style.right = 'unset';
412
+ this.widgetWrapper.style.flexDirection = 'row-reverse';
413
+ }
414
+
415
+ // Try to apply saved position after a short delay (to ensure elements are rendered)
416
+ setTimeout(() => {
417
+ this.applyWidgetPosition();
418
+ }, 100);
419
+
340
420
  // Create backdrop
341
421
  this.backdrop = document.createElement("div");
342
422
  this.backdrop.className = "cta__modal-dimmer";
@@ -418,11 +498,9 @@ export default class RbirdWebsiteWidget {
418
498
  };
419
499
  http.onreadystatechange = function () {
420
500
  if (http.readyState === XMLHttpRequest.DONE) {
421
- console.log('[RbirdWidget] Unread messages response:', http.status, http.responseText);
422
501
  if (http.status === 200 || http.status === 201) {
423
502
  try {
424
503
  const messages = JSON.parse(http.responseText);
425
- console.log('[RbirdWidget] Parsed messages:', messages);
426
504
  that.renderMessageBubbles(messages);
427
505
  } catch (e) {
428
506
  console.error('[RbirdWidget] Error parsing unread messages:', e);
@@ -438,12 +516,10 @@ export default class RbirdWebsiteWidget {
438
516
  * @param {Array} messages - Array of unread messages (one per chat, max 5)
439
517
  */
440
518
  renderMessageBubbles(messages) {
441
- console.log('[RbirdWidget] renderMessageBubbles called with:', messages);
442
519
  if (typeof window === 'undefined' || typeof document === 'undefined') return;
443
520
 
444
521
  // Don't show bubbles if widget is open
445
522
  if (this.isOpen()) {
446
- console.log('[RbirdWidget] Widget is open, hiding bubbles');
447
523
  this.hideMessageBubbles();
448
524
  return;
449
525
  }
@@ -453,10 +529,8 @@ export default class RbirdWebsiteWidget {
453
529
  .filter(msg => !this.dismissedBubbles.has(msg.chatId))
454
530
  .slice(0, 5);
455
531
 
456
- console.log('[RbirdWidget] Filtered messages:', filteredMessages);
457
532
 
458
533
  if (filteredMessages.length === 0) {
459
- console.log('[RbirdWidget] No messages to display');
460
534
  this.hideMessageBubbles();
461
535
  return;
462
536
  }
@@ -618,4 +692,282 @@ export default class RbirdWebsiteWidget {
618
692
  }
619
693
  }
620
694
 
695
+ /**
696
+ * Create the drag handle element (6-dot grip pattern)
697
+ */
698
+ createDragHandle() {
699
+ const handle = document.createElement('div');
700
+ handle.className = 'rbird-drag-handle';
701
+
702
+ // Create 3 rows of 2 dots each
703
+ for (let i = 0; i < 3; i++) {
704
+ const row = document.createElement('div');
705
+ row.className = 'rbird-drag-handle-row';
706
+ for (let j = 0; j < 2; j++) {
707
+ const dot = document.createElement('div');
708
+ dot.className = 'rbird-drag-handle-dot';
709
+ row.appendChild(dot);
710
+ }
711
+ handle.appendChild(row);
712
+ }
713
+
714
+ return handle;
715
+ }
716
+
717
+ /**
718
+ * Initialize drag event listeners
719
+ */
720
+ initDragListeners() {
721
+ if (!this.dragHandle) return;
722
+
723
+ // Mouse events
724
+ this.dragHandle.addEventListener('mousedown', (e) => this.startDrag(e));
725
+ document.addEventListener('mousemove', (e) => this.onDrag(e));
726
+ document.addEventListener('mouseup', (e) => this.endDrag(e));
727
+
728
+ // Touch events for mobile
729
+ this.dragHandle.addEventListener('touchstart', (e) => this.startDrag(e), { passive: false });
730
+ document.addEventListener('touchmove', (e) => this.onDrag(e), { passive: false });
731
+ document.addEventListener('touchend', (e) => this.endDrag(e));
732
+ }
733
+
734
+ /**
735
+ * Start dragging
736
+ */
737
+ startDrag(e) {
738
+ if (this.isMobileDevice()) return; // Disable dragging on mobile for now
739
+
740
+ e.preventDefault();
741
+ this.isDragging = true;
742
+ this.widgetWrapper.classList.add('rbird-dragging');
743
+
744
+ const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
745
+ const clientY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY;
746
+
747
+ this.dragStartX = clientX;
748
+ this.dragStartY = clientY;
749
+
750
+ const rect = this.widgetWrapper.getBoundingClientRect();
751
+ this.initialX = rect.left;
752
+ this.initialY = rect.top;
753
+ }
754
+
755
+ /**
756
+ * Handle drag movement
757
+ */
758
+ onDrag(e) {
759
+ if (!this.isDragging) return;
760
+
761
+ e.preventDefault();
762
+
763
+ const clientX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
764
+ const clientY = e.type.includes('touch') ? e.touches[0].clientY : e.clientY;
765
+
766
+ const deltaX = clientX - this.dragStartX;
767
+ const deltaY = clientY - this.dragStartY;
768
+
769
+ let newX = this.initialX + deltaX;
770
+ let newY = this.initialY + deltaY;
771
+
772
+ // Constrain to viewport
773
+ const wrapperRect = this.widgetWrapper.getBoundingClientRect();
774
+ const maxX = window.innerWidth - wrapperRect.width;
775
+ const maxY = window.innerHeight - wrapperRect.height;
776
+
777
+ newX = Math.max(0, Math.min(newX, maxX));
778
+ newY = Math.max(0, Math.min(newY, maxY));
779
+
780
+ this.updateWidgetPosition(newX, newY);
781
+ }
782
+
783
+ /**
784
+ * End dragging and save position
785
+ */
786
+ endDrag(e) {
787
+ if (!this.isDragging) return;
788
+
789
+ this.isDragging = false;
790
+ this.widgetWrapper.classList.remove('rbird-dragging');
791
+
792
+ // Save position to localStorage
793
+ const rect = this.widgetWrapper.getBoundingClientRect();
794
+ this.saveWidgetPosition(rect.left, rect.top);
795
+ }
796
+
797
+ /**
798
+ * Update widget and related elements position
799
+ */
800
+ updateWidgetPosition(x, y) {
801
+ if (!this.widgetWrapper) return;
802
+
803
+ // Update wrapper position
804
+ this.widgetWrapper.style.left = `${x}px`;
805
+ this.widgetWrapper.style.right = 'unset';
806
+ this.widgetWrapper.style.top = 'unset';
807
+ this.widgetWrapper.style.bottom = `${window.innerHeight - y - this.widgetWrapper.offsetHeight}px`;
808
+
809
+ // Update badge position
810
+ if (this.countBadge) {
811
+ const buttonRect = this.websiteWidget.getBoundingClientRect();
812
+ this.countBadge.style.left = `${buttonRect.right - 15}px`;
813
+ this.countBadge.style.right = 'unset';
814
+ this.countBadge.style.bottom = `${window.innerHeight - buttonRect.top + 5}px`;
815
+ }
816
+
817
+ // Update hide button position (top right of bubble)
818
+ if (this.hideWidgetButton) {
819
+ const buttonRect = this.websiteWidget.getBoundingClientRect();
820
+ this.hideWidgetButton.style.left = `${buttonRect.right - 5}px`;
821
+ this.hideWidgetButton.style.right = 'unset';
822
+ this.hideWidgetButton.style.bottom = `${window.innerHeight - buttonRect.top + 5}px`;
823
+ }
824
+
825
+ // Update message bubbles container position
826
+ if (this.messageBubblesContainer) {
827
+ const buttonRect = this.websiteWidget.getBoundingClientRect();
828
+ this.messageBubblesContainer.style.left = `${buttonRect.left}px`;
829
+ this.messageBubblesContainer.style.right = 'unset';
830
+ this.messageBubblesContainer.style.bottom = `${window.innerHeight - buttonRect.top + 20}px`;
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Save widget position to localStorage
836
+ */
837
+ saveWidgetPosition(x, y) {
838
+ try {
839
+ const position = { x, y, timestamp: Date.now() };
840
+ window.localStorage.setItem('rbird_widget_position', JSON.stringify(position));
841
+ } catch (e) {
842
+ console.warn('[RbirdWidget] Could not save position to localStorage:', e);
843
+ }
844
+ }
845
+
846
+ /**
847
+ * Load widget position from localStorage
848
+ */
849
+ loadWidgetPosition() {
850
+ try {
851
+ const saved = window.localStorage.getItem('rbird_widget_position');
852
+ if (saved) {
853
+ const position = JSON.parse(saved);
854
+ // Validate position is still within viewport
855
+ if (position.x >= 0 && position.x < window.innerWidth &&
856
+ position.y >= 0 && position.y < window.innerHeight) {
857
+ return position;
858
+ }
859
+ }
860
+ } catch (e) {
861
+ console.warn('[RbirdWidget] Could not load position from localStorage:', e);
862
+ }
863
+ return null;
864
+ }
865
+
866
+ /**
867
+ * Apply saved position or use default
868
+ */
869
+ applyWidgetPosition() {
870
+ const savedPosition = this.loadWidgetPosition();
871
+
872
+ if (savedPosition && this.widgetWrapper) {
873
+ // Apply saved position
874
+ this.updateWidgetPosition(savedPosition.x, savedPosition.y);
875
+ // Update related elements after a short delay to ensure DOM is ready
876
+ setTimeout(() => {
877
+ this.updateRelatedElementsPosition();
878
+ }, 50);
879
+ return true;
880
+ }
881
+ return false;
882
+ }
883
+
884
+ /**
885
+ * Update positions of badge, hide button, and message bubbles based on current button position
886
+ */
887
+ updateRelatedElementsPosition() {
888
+ if (!this.websiteWidget) return;
889
+
890
+ const buttonRect = this.websiteWidget.getBoundingClientRect();
891
+
892
+ // Check if we have a custom position (not default)
893
+ const savedPosition = this.loadWidgetPosition();
894
+ if (!savedPosition) return; // Use default CSS positioning
895
+
896
+ // Update badge position
897
+ if (this.countBadge) {
898
+ this.countBadge.style.left = `${buttonRect.right - 15}px`;
899
+ this.countBadge.style.right = 'unset';
900
+ this.countBadge.style.bottom = `${window.innerHeight - buttonRect.top + 5}px`;
901
+ }
902
+
903
+ // Update hide button position (top right of bubble)
904
+ if (this.hideWidgetButton) {
905
+ this.hideWidgetButton.style.left = `${buttonRect.right - 5}px`;
906
+ this.hideWidgetButton.style.right = 'unset';
907
+ this.hideWidgetButton.style.bottom = `${window.innerHeight - buttonRect.top + 5}px`;
908
+ }
909
+
910
+ // Update message bubbles container position
911
+ if (this.messageBubblesContainer) {
912
+ this.messageBubblesContainer.style.left = `${buttonRect.left}px`;
913
+ this.messageBubblesContainer.style.right = 'unset';
914
+ this.messageBubblesContainer.style.bottom = `${window.innerHeight - buttonRect.top + 20}px`;
915
+ }
916
+ }
917
+
918
+ /**
919
+ * Position the widget content (modal) relative to the bubble position
920
+ */
921
+ positionWidgetContent() {
922
+ if (!this.widgetContent || !this.websiteWidget || this.isMobileDevice()) return;
923
+
924
+ let buttonRect = this.websiteWidget.getBoundingClientRect();
925
+ const modalWidth = 450; // Default modal width
926
+ const modalHeight = 725; // Default modal height
927
+ const padding = 10;
928
+ const gap = 5; // Small gap between bubble and modal
929
+
930
+ // Check if there's enough space above the bubble for the modal
931
+ const spaceAbove = buttonRect.top - gap - padding;
932
+ const effectiveModalHeight = Math.min(modalHeight, window.innerHeight - 100); // Leave some space
933
+
934
+ if (spaceAbove < effectiveModalHeight) {
935
+ // Not enough space - move bubble down
936
+ const neededSpace = effectiveModalHeight + gap + padding;
937
+ const newBottomFromViewport = window.innerHeight - neededSpace - buttonRect.height;
938
+
939
+ // Update bubble position
940
+ if (this.widgetWrapper) {
941
+ this.widgetWrapper.style.bottom = `${newBottomFromViewport}px`;
942
+ // Save the new position
943
+ const wrapperRect = this.widgetWrapper.getBoundingClientRect();
944
+ this.saveWidgetPosition(wrapperRect.left, wrapperRect.top);
945
+ }
946
+
947
+ // Get updated button position
948
+ buttonRect = this.websiteWidget.getBoundingClientRect();
949
+ }
950
+
951
+ // Horizontal position - align modal's right edge with bubble's right edge
952
+ const rightFromViewport = window.innerWidth - buttonRect.right;
953
+ this.widgetContent.style.right = `${rightFromViewport}px`;
954
+ this.widgetContent.style.left = 'unset';
955
+
956
+ // Check if modal would go off-screen on the left
957
+ const modalLeft = buttonRect.right - modalWidth;
958
+ if (modalLeft < padding) {
959
+ // Shift modal to the right to keep it on screen
960
+ this.widgetContent.style.left = `${padding}px`;
961
+ this.widgetContent.style.right = 'unset';
962
+ }
963
+
964
+ // Vertical position - directly above the button with small gap
965
+ const bottomFromViewport = window.innerHeight - buttonRect.top + gap;
966
+ this.widgetContent.style.bottom = `${bottomFromViewport}px`;
967
+ this.widgetContent.style.top = 'unset';
968
+
969
+ // Reset max-height to default
970
+ this.widgetContent.style.maxHeight = '';
971
+ }
972
+
621
973
  }