aumera-on-screen-widget 0.0.8 → 0.0.9

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.
Files changed (2) hide show
  1. package/df-btn.js +140 -18
  2. package/package.json +1 -1
package/df-btn.js CHANGED
@@ -34,7 +34,10 @@ if (!config.orgId) {
34
34
  background-color: ${config.background || "#FEFFFF"};
35
35
  border-radius: 24px;
36
36
  cursor: pointer;
37
- transition: all .45s cubic-bezier(.4, 0, .2, 1);
37
+ transition: none;
38
+ transform-origin: ${
39
+ config.position === "left" ? "bottom left" : "bottom right"
40
+ };
38
41
  position: fixed;
39
42
  bottom: 0px;
40
43
  ${config.position === "left" ? "left: 0px;" : "right: 0px;"}
@@ -130,7 +133,10 @@ if (!config.orgId) {
130
133
  max-height: 75vh;
131
134
  width: ${config.width || "414px"};
132
135
  max-width: 414px;
133
- transition: all .45s cubic-bezier(.4, 0, .2, 1);
136
+ transition: none;
137
+ transform-origin: ${
138
+ config.position === "left" ? "bottom left" : "bottom right"
139
+ };
134
140
  ${config.position === "left" ? "float: left;" : "float: right;"}
135
141
  opacity: 1;
136
142
  border-radius: 0 0 16px 16px;
@@ -160,24 +166,27 @@ if (!config.orgId) {
160
166
  .df-btn.df-maximized {
161
167
  width: 100vw !important;
162
168
  height: 100vh !important;
169
+ height: 100dvh !important; /* Dynamic viewport height for mobile */
163
170
  top: 0 !important;
164
- left: 0 !important;
165
- right: 0 !important;
171
+ ${config.position === "left" ? "left: 0 !important;" : ""}
172
+ ${config.position === "right" ? "right: 0 !important;" : ""}
166
173
  bottom: 0 !important;
167
174
  margin: 0 !important;
168
175
  border-radius: 0 !important;
169
- transition: all .45s cubic-bezier(.4, 0, .2, 1) !important;
176
+ /* Handle safe areas on mobile devices */
177
+ padding-bottom: env(safe-area-inset-bottom, 0);
170
178
  }
171
179
  .df-btn.df-maximized .df-btn-content {
172
180
  width: 100vw !important;
173
181
  max-width: 100vw !important;
174
182
  height: calc(100vh - 56px) !important;
183
+ height: calc(100dvh - env(safe-area-inset-bottom, 0px)) !important; /* Account for safe area */
175
184
  max-height: calc(100vh - 56px) !important;
185
+ max-height: calc(100dvh - env(safe-area-inset-bottom, 0px)) !important;
176
186
  opacity: 1 !important;
177
-
187
+ float: none !important;
178
188
  display: block !important;
179
189
  border-radius: 0 !important;
180
- transition: all .45s cubic-bezier(.4, 0, .2, 1) !important;
181
190
  }
182
191
  .df-btn.df-closed .df-btn-content {
183
192
  width: 0 !important;
@@ -194,6 +203,17 @@ if (!config.orgId) {
194
203
  justify-content: center;
195
204
  }
196
205
 
206
+ /* Animation classes for FLIP */
207
+ .df-animating {
208
+ transition: transform .45s cubic-bezier(.4, 0, .2, 1);
209
+ will-change: transform;
210
+ }
211
+
212
+ .df-content-animating {
213
+ transition: opacity .45s cubic-bezier(.4, 0, .2, 1);
214
+ will-change: opacity;
215
+ }
216
+
197
217
  @media screen and (max-width: 720px){
198
218
  .df-btn {
199
219
  border-radius: 28px;
@@ -201,13 +221,16 @@ if (!config.orgId) {
201
221
 
202
222
  .df-btn:not(.df-closed) {
203
223
  margin: 0px;
204
- border-radius: 0px
224
+ border-radius: 0px;
225
+ padding-bottom: env(safe-area-inset-bottom, 0);
205
226
  }
206
227
 
207
228
  .df-btn:not(.df-closed) > .df-btn-content {
208
229
  width: 100vw;
209
230
  max-height: 100vh;
231
+ max-height: 100dvh;
210
232
  height: calc(100vh - 56px);
233
+ height: calc(100dvh - 56px - env(safe-area-inset-bottom, 0px));
211
234
  padding-bottom: 0px
212
235
  }
213
236
 
@@ -276,8 +299,7 @@ if (!config.orgId) {
276
299
  // Get the detected language
277
300
  const detectedLang = getLanguage();
278
301
 
279
- // Check if mobile device
280
- const isMobileDevice = window.innerWidth <= 768;
302
+ // Check if mobile device - removed unused variable
281
303
 
282
304
  document.write(`
283
305
  <button class="df-btn df-closed" onclick="dfToggle()">
@@ -298,7 +320,6 @@ if (!config.orgId) {
298
320
  let dfToggled = false;
299
321
  let inactivityTimer = null;
300
322
  let shakeInterval = null;
301
- let maximized = false;
302
323
 
303
324
  const startInactivityTimer = () => {
304
325
  // Only start inactivity timer if button is closed
@@ -361,7 +382,6 @@ if (!config.orgId) {
361
382
  btnText.style.paddingRight = "0";
362
383
 
363
384
  // Only add maximize/minimize button on desktop
364
- const isMobile = window.innerWidth <= 768;
365
385
 
366
386
  // Add maximize/minimize button (right)
367
387
  const maxBtn = document.createElement("div");
@@ -405,8 +425,56 @@ if (!config.orgId) {
405
425
  function maximizeMinimize() {
406
426
  const btn = document.querySelector(".df-btn");
407
427
  if (!btn) return;
428
+ const content = btn.querySelector(".df-btn-content");
429
+ if (!content) return;
430
+
431
+ // FIRST: Capture current positions
432
+ const firstBtn = btn.getBoundingClientRect();
433
+
434
+ // Toggle the maximized state (instant layout change)
408
435
  btn.classList.toggle("df-maximized");
409
- updateHeaderButtons();
436
+
437
+ // LAST: Capture new positions
438
+ const lastBtn = btn.getBoundingClientRect();
439
+
440
+ // INVERT: Calculate the delta and apply transform to invert to original position
441
+ const btnDeltaX = firstBtn.left - lastBtn.left;
442
+ const btnDeltaY = firstBtn.top - lastBtn.top;
443
+ const btnScaleX = firstBtn.width / Math.max(lastBtn.width, 0.0001);
444
+ const btnScaleY = firstBtn.height / Math.max(lastBtn.height, 0.0001);
445
+
446
+ // Apply inverted transform
447
+ btn.style.transform = `translate(${btnDeltaX}px, ${btnDeltaY}px) scale(${btnScaleX}, ${btnScaleY})`;
448
+
449
+ // Optional: Soften iframe reflow with opacity
450
+ content.classList.add("df-content-animating");
451
+ content.style.opacity = "0.95";
452
+
453
+ // Force reflow to ensure the inverted state is applied
454
+ btn.getBoundingClientRect();
455
+
456
+ // PLAY: Add animation class and clear transform to animate to identity
457
+ btn.classList.add("df-animating");
458
+ btn.style.transform = "";
459
+
460
+ // Cleanup after animation ends
461
+ const onTransitionEnd = (e) => {
462
+ if (e.target !== btn) return;
463
+ btn.classList.remove("df-animating");
464
+ content.classList.remove("df-content-animating");
465
+ content.style.opacity = "";
466
+ updateHeaderButtons();
467
+ };
468
+
469
+ btn.addEventListener("transitionend", onTransitionEnd, { once: true });
470
+
471
+ // Fallback cleanup in case transition doesn't fire
472
+ setTimeout(() => {
473
+ btn.classList.remove("df-animating");
474
+ content.classList.remove("df-content-animating");
475
+ content.style.opacity = "";
476
+ updateHeaderButtons();
477
+ }, 500);
410
478
  }
411
479
 
412
480
  function dfToggle() {
@@ -434,14 +502,68 @@ if (!config.orgId) {
434
502
  // Check if mobile for later use
435
503
  const isMobile = window.innerWidth <= 768;
436
504
 
437
- btn.addEventListener("click", (e) => {
505
+ btn.addEventListener("click", () => {
438
506
  // Only toggle if chat is closed (popover mode)
439
507
  if (btn.classList.contains("df-closed")) {
440
- dfToggle();
441
- // On mobile, also add maximized state when opening
442
- if (window.innerWidth <= 768) {
508
+ const isMobile = window.innerWidth <= 768;
509
+
510
+ if (isMobile) {
511
+ // On mobile, apply FLIP animation for open+maximize
512
+ const content = btn.querySelector(".df-btn-content");
513
+
514
+ // FIRST: Capture current (closed) position
515
+ const firstBtn = btn.getBoundingClientRect();
516
+
517
+ // Open and maximize instantly
518
+ btn.classList.remove("df-closed");
443
519
  btn.classList.add("df-maximized");
444
- updateHeaderButtons();
520
+
521
+ // LAST: Capture final position
522
+ const lastBtn = btn.getBoundingClientRect();
523
+
524
+ // INVERT
525
+ const btnDeltaX = firstBtn.left - lastBtn.left;
526
+ const btnDeltaY = firstBtn.top - lastBtn.top;
527
+ const btnScaleX = firstBtn.width / Math.max(lastBtn.width, 0.0001);
528
+ const btnScaleY = firstBtn.height / Math.max(lastBtn.height, 0.0001);
529
+
530
+ btn.style.transform = `translate(${btnDeltaX}px, ${btnDeltaY}px) scale(${btnScaleX}, ${btnScaleY})`;
531
+
532
+ if (content) {
533
+ content.classList.add("df-content-animating");
534
+ content.style.opacity = "0.95";
535
+ }
536
+
537
+ // Force reflow
538
+ btn.getBoundingClientRect();
539
+
540
+ // PLAY
541
+ btn.classList.add("df-animating");
542
+ btn.style.transform = "";
543
+
544
+ // Cleanup
545
+ const onTransitionEnd = () => {
546
+ btn.classList.remove("df-animating");
547
+ if (content) {
548
+ content.classList.remove("df-content-animating");
549
+ content.style.opacity = "";
550
+ }
551
+ updateHeaderButtons();
552
+ };
553
+
554
+ btn.addEventListener("transitionend", onTransitionEnd, { once: true });
555
+
556
+ setTimeout(() => {
557
+ btn.classList.remove("df-animating");
558
+ if (content) {
559
+ content.classList.remove("df-content-animating");
560
+ content.style.opacity = "";
561
+ }
562
+ updateHeaderButtons();
563
+ }, 500);
564
+ } else {
565
+ // Desktop: just toggle open
566
+ dfToggle();
445
567
  }
446
568
  }
447
569
  // If open, do nothing (let header buttons handle maximize/minimize/close)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aumera-on-screen-widget",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "A lightweight, customizable chat widget for websites",
5
5
  "main": "df-btn.js",
6
6
  "scripts": {