@magicfeedback/popup-sdk 1.7.1 → 1.7.2

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/dist/index.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  InMemoryStorage,
3
3
  LABELS,
4
+ PRIMARY_COLOR_JS,
4
5
  RESOLVE_LOCALE_JS,
6
+ REVEAL_TIMEOUT_MS,
5
7
  RTL_LOCALES,
6
8
  STORAGE_KEYS,
7
9
  SUPPORTED_LANGUAGES,
@@ -18,7 +20,7 @@ import {
18
20
  sdkWarn,
19
21
  setLogger,
20
22
  style_default
21
- } from "./chunk-W6MPSWMS.mjs";
23
+ } from "./chunk-XHR3MHJO.mjs";
22
24
 
23
25
  // src/types/index.ts
24
26
  var POPUP_TRIGGER_CONDITION_STATUSES = [
@@ -209,6 +211,30 @@ function resolveEnvironment(nodeEnv) {
209
211
  }
210
212
 
211
213
  // src/ui/surveyHtml.ts
214
+ var REVEAL_JS = `
215
+ function ddCreateReveal(popup, onReveal, timeoutMs){
216
+ var revealed=false, timer=null;
217
+ function reveal(){
218
+ if(revealed){ return; }
219
+ revealed=true;
220
+ if(timer){ clearTimeout(timer); timer=null; }
221
+ onReveal();
222
+ }
223
+ function revealWhenPainted(){
224
+ if(revealed){ return; }
225
+ var imgs=popup.querySelectorAll('img'), pending=[];
226
+ for(var i=0;i<imgs.length;i++){ if(!imgs[i].complete){ pending.push(imgs[i]); } }
227
+ if(!pending.length){ reveal(); return; }
228
+ var left=pending.length;
229
+ function onSettled(){ left--; if(left<=0){ reveal(); } }
230
+ for(var j=0;j<pending.length;j++){
231
+ pending[j].addEventListener('load', onSettled);
232
+ pending[j].addEventListener('error', onSettled);
233
+ }
234
+ }
235
+ timer=setTimeout(reveal, timeoutMs);
236
+ return { reveal: reveal, whenPainted: revealWhenPainted };
237
+ }`;
212
238
  var POSITION_MAP = {
213
239
  center: { justifyContent: "center", alignItems: "center", padding: "0", background: "rgba(0,0,0,0.5)" },
214
240
  bottom: { justifyContent: "center", alignItems: "flex-end", padding: "16px", background: "transparent" },
@@ -282,7 +308,13 @@ function buildSurveyHtml(opts) {
282
308
  ${style_default}
283
309
  ${fontFaceCss}
284
310
  html,body{margin:0;padding:0;height:100%;font-family:${fontFamilyValue}}
285
- body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:${bodyBg};padding:${bodyPadding};box-sizing:border-box}
311
+ /* Apertura diferida: ni la tarjeta ni el velo se pintan hasta que el survey est\xE1 listo
312
+ (\`dd-ready\`). Si el fondo entrara antes, en RN se ver\xEDa el velo oscuro varios cientos de ms
313
+ antes que el contenido, que es la misma mala sensaci\xF3n que el spinner. */
314
+ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:transparent;padding:${bodyPadding};box-sizing:border-box}
315
+ body.dd-ready{background:${bodyBg}}
316
+ #dd-popup{visibility:hidden}
317
+ body.dd-ready #dd-popup{visibility:visible}
286
318
  .deepdots-popup{position:relative;display:flex;flex-direction:column;justify-content:flex-start;background:${cardBg};color-scheme:${colorScheme};border-radius:${cardRadius};padding:24px;box-shadow:${cardShadow};max-width:${cardMaxWidth};width:${cardWidth};min-height:${cardMinHeight};height:${cardHeight};max-height:${cardMaxHeight};box-sizing:border-box}
287
319
  .deepdots-popup-header{display:flex;justify-content:space-between;align-items:center;gap:12px;width:100%;flex:0 0 auto}
288
320
  /* Neutraliza la regla \`.deepdots-popup h2\` del CSS del survey (uppercase, centrado y
@@ -316,6 +348,13 @@ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${
316
348
  #dd-form-wrapper{width:100%;flex:1 1 auto}
317
349
  #mf{width:100%;box-sizing:border-box;visibility:hidden}
318
350
  .deepdots-popup-main *{max-width:100%;box-sizing:border-box}
351
+ /* El margen lateral lo pone la tarjeta: el CSS del survey lo a\xF1ade otra vez en el contenedor, en
352
+ el formulario y en el bloque de cada pregunta, y eso deja el enunciado y las opciones m\xE1s
353
+ adentro que el logo, el t\xEDtulo y la barra de progreso. Solo el lateral; el vertical separa las
354
+ preguntas. Mismas reglas que en el popup DOM (renderPopup.ts). */
355
+ .deepdots-popup .magicfeedback-container,
356
+ .deepdots-popup .magicfeedback-form,
357
+ .deepdots-popup .magicfeedback-div{padding-left:0;padding-right:0}
319
358
  #dd-logo{max-height:40px;max-width:100%;object-fit:contain;display:block;margin:12px 0 0 0}
320
359
  .deepdots-error-hint{display:none;margin:12px 0 0 0;padding:10px 12px;border-radius:6px;background:#FEF3C7;color:#92400E;border:1px solid #FCD34D;font-size:13px}
321
360
  /* Pantalla final: el HTML del editor de la plataforma (imagen + texto), centrado. */
@@ -323,7 +362,7 @@ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${
323
362
  .deepdots-success img{max-width:100%;height:auto;margin:0 auto 16px auto;display:block}
324
363
  .deepdots-success p{margin:0;font-size:16px;font-weight:600;line-height:1.4}
325
364
  .deepdots-popup-footer{display:flex;flex-direction:row-reverse;justify-content:space-between;align-items:center;gap:4px;margin-top:auto;width:100%;padding-top:12px}
326
- .dd-nav-btn{display:none;border:none;min-height:44px;padding:12px 24px;border-radius:6px;cursor:pointer;font-size:15px;font-weight:600;align-items:center;justify-content:center;text-align:center}
365
+ .dd-nav-btn{display:none;border:none;min-height:44px;padding:12px 24px;border-radius:999px;cursor:pointer;font-size:15px;font-weight:600;align-items:center;justify-content:center;text-align:center}
327
366
  /* Secundario como bot\xF3n de texto: sin borde ni fondo, para que el primario sea la \xFAnica CTA. */
328
367
  #dd-back{background:transparent;color:${textMuted};border:none}
329
368
  #dd-start,#dd-complete,#dd-submit{background:#1E293B;color:#fff;border:none}
@@ -548,9 +587,23 @@ ${customCss}
548
587
  }
549
588
  }
550
589
 
590
+ ${PRIMARY_COLOR_JS}
591
+ ${REVEAL_JS}
592
+ // El popup se ense\xF1a cuando el survey est\xE1 pintado; \`ready\` deja al host abrir su propio
593
+ // Modal en ese momento en vez de montar un WebView en blanco.
594
+ var ddReveal=ddCreateReveal(popup, function(){
595
+ document.body.classList.add('dd-ready');
596
+ emitJSON('ready');
597
+ }, ${REVEAL_TIMEOUT_MS});
598
+
551
599
  function setLoading(isLoading){
552
600
  spinner.style.display=isLoading?'flex':'none';
553
- if(!isLoading){ formHost.style.visibility='visible'; }
601
+ if(!isLoading){
602
+ formHost.style.visibility='visible';
603
+ // Fin de la carga, sea por survey cargado o por error. Entre p\xE1ginas es no-op: la
604
+ // revelaci\xF3n solo act\xFAa la primera vez.
605
+ ddReveal.whenPainted();
606
+ }
554
607
  footer.style.display=isLoading?'none':'flex';
555
608
  backBtn.disabled=isLoading; startBtn.disabled=isLoading; completeBtn.disabled=isLoading; submitBtn.disabled=isLoading;
556
609
  if(isLoading){ updateButtons(null); }
@@ -593,6 +646,10 @@ ${customCss}
593
646
  if(s.loadingBarColor){ progressBar.style.background=s.loadingBarColor; progressFollowUp.style.background=s.loadingBarColor; }
594
647
  if(chromeOn && s.boxBackgroundColor){ popup.style.background=s.boxBackgroundColor; }
595
648
  if(s.contentAlign){ main.style.justifyContent = s.contentAlign==='center' ? 'center' : 'flex-start'; }
649
+ // El survey pinta sus controles con --mf-primary, que sale de primaryColor; si la
650
+ // integraci\xF3n solo configura el del bot\xF3n, el popup mezclar\xEDa el color de la marca
651
+ // con el gris azulado por defecto del paquete.
652
+ ddApplySurveyPrimaryColor(popup, s);
596
653
  if(s.buttonPrimaryColor){
597
654
  submitBtn.style.background=s.buttonPrimaryColor; submitBtn.style.border='none'; submitBtn.style.color='#fff';
598
655
  startBtn.style.background=s.buttonPrimaryColor; startBtn.style.border='none'; startBtn.style.color='#fff';
@@ -684,6 +741,7 @@ ${customCss}
684
741
  }
685
742
 
686
743
  // src/platform/react-native-renderer.ts
744
+ var NATIVE_READY_GRACE_MS = 500;
687
745
  var ReactNativePopupRenderer = class {
688
746
  constructor(options = {}) {
689
747
  this.options = options;
@@ -691,6 +749,8 @@ var ReactNativePopupRenderer = class {
691
749
  this.onCloseFn = null;
692
750
  this.currentSurveyId = null;
693
751
  this.partialEmitted = false;
752
+ this.readyEmitted = false;
753
+ this.readyTimer = null;
694
754
  }
695
755
  init() {
696
756
  }
@@ -699,6 +759,8 @@ var ReactNativePopupRenderer = class {
699
759
  this.onCloseFn = onClose;
700
760
  this.currentSurveyId = surveyId;
701
761
  this.partialEmitted = false;
762
+ this.readyEmitted = false;
763
+ this.armReadyFallback(surveyId);
702
764
  const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
703
765
  const html = buildSurveyHtml({
704
766
  surveyId,
@@ -726,9 +788,32 @@ var ReactNativePopupRenderer = class {
726
788
  }
727
789
  }
728
790
  hide() {
791
+ this.clearReadyFallback();
729
792
  this.options.onHide?.();
730
793
  this.currentSurveyId = null;
731
794
  }
795
+ /** Respaldo del aviso de listo por si el WebView no llega a ejecutar el HTML. */
796
+ armReadyFallback(surveyId) {
797
+ this.clearReadyFallback();
798
+ if (!this.options.onReady) return;
799
+ this.readyTimer = setTimeout(() => {
800
+ this.readyTimer = null;
801
+ this.emitReady(surveyId);
802
+ }, REVEAL_TIMEOUT_MS + NATIVE_READY_GRACE_MS);
803
+ }
804
+ clearReadyFallback() {
805
+ if (this.readyTimer !== null) {
806
+ clearTimeout(this.readyTimer);
807
+ this.readyTimer = null;
808
+ }
809
+ }
810
+ /** Idempotente: el WebView puede avisar a la vez que vence el respaldo. */
811
+ emitReady(surveyId) {
812
+ if (this.readyEmitted) return;
813
+ this.readyEmitted = true;
814
+ this.clearReadyFallback();
815
+ this.options.onReady?.({ surveyId });
816
+ }
732
817
  /**
733
818
  * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
734
819
  * eventos del SDK: primera interacción → `popup_clicked` (PARTIAL), completado →
@@ -744,6 +829,9 @@ var ReactNativePopupRenderer = class {
744
829
  name = raw;
745
830
  }
746
831
  switch (name) {
832
+ case "ready":
833
+ this.emitReady(surveyId);
834
+ break;
747
835
  case "loaded":
748
836
  case "before_submit":
749
837
  case "after_submit":
@@ -771,6 +859,33 @@ function createReactNativeRenderer() {
771
859
  return new ReactNativePopupRenderer();
772
860
  }
773
861
 
862
+ // src/ui/preload.ts
863
+ var defaultLoader = () => import("./renderPopup-5QXL6EJW.mjs");
864
+ var pending = null;
865
+ function preloadRenderPopup(loader = defaultLoader) {
866
+ if (pending) return pending;
867
+ try {
868
+ pending = loader().then(() => void 0).catch(() => {
869
+ pending = null;
870
+ });
871
+ } catch {
872
+ pending = null;
873
+ return Promise.resolve();
874
+ }
875
+ return pending;
876
+ }
877
+ function schedulePreloadRenderPopup(loader) {
878
+ const run = () => {
879
+ void preloadRenderPopup(loader);
880
+ };
881
+ const idle = globalThis.requestIdleCallback;
882
+ if (typeof idle === "function") {
883
+ idle(run, { timeout: 2e3 });
884
+ return;
885
+ }
886
+ setTimeout(run, 0);
887
+ }
888
+
774
889
  // src/platform/renderer.ts
775
890
  var NoopPopupRenderer = class {
776
891
  show() {
@@ -805,13 +920,20 @@ var BrowserPopupRenderer = class {
805
920
  document.body.appendChild(this.container);
806
921
  }
807
922
  }
923
+ /**
924
+ * Descarga el chunk de `renderPopup` antes de que ningún trigger dispare, para que abrir el
925
+ * popup no empiece por esperar ~238 KB de JS.
926
+ */
927
+ preload() {
928
+ schedulePreloadRenderPopup();
929
+ }
808
930
  show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId, _renderChrome, options) {
809
931
  if (this.visible) return;
810
932
  if (!this.container || !document.body.contains(this.container)) this.init();
811
933
  if (!this.container) return;
812
934
  this.visible = true;
813
935
  const container = this.container;
814
- void import("./renderPopup-HHQMAW3G.mjs").then(({ renderPopup }) => {
936
+ void import("./renderPopup-5QXL6EJW.mjs").then(({ renderPopup }) => {
815
937
  renderPopup(container, surveyId, productId, actions, emit, onClose, env, userId, style, sessionId, miniService, analyticsFeedbackSessionId, options);
816
938
  });
817
939
  }
@@ -819,6 +941,8 @@ var BrowserPopupRenderer = class {
819
941
  this.visible = false;
820
942
  if (this.container) {
821
943
  this.container.style.display = "none";
944
+ this.container.style.visibility = "";
945
+ this.container.style.pointerEvents = "";
822
946
  this.container.innerHTML = "";
823
947
  }
824
948
  }
@@ -1888,6 +2012,7 @@ var DeepdotsPopups = class {
1888
2012
  this.popupDefinitions = this.validatePopupDefinitions(defs);
1889
2013
  this.popupsLoaded = true;
1890
2014
  this.log("Popups loaded from API", this.popupDefinitions);
2015
+ if (this.popupDefinitions.length) this.renderer.preload?.();
1891
2016
  this.configureTriggersFromDefinitions();
1892
2017
  this.processDeferredExitQueue();
1893
2018
  if (this.pendingAutoLaunch) {
@@ -2375,7 +2500,7 @@ var DeepdotsPopups = class {
2375
2500
  processDeferredExitQueue() {
2376
2501
  const queue = this.getDeferredExitQueue();
2377
2502
  if (!queue.length) return;
2378
- const pending = [];
2503
+ const pending2 = [];
2379
2504
  const now = Date.now();
2380
2505
  queue.forEach((item) => {
2381
2506
  const def = this.popupDefinitions.find((popup) => popup.id === item.id && popup.surveyId === item.surveyId);
@@ -2386,10 +2511,10 @@ var DeepdotsPopups = class {
2386
2511
  this.tryShowDeferredExitPopup(item);
2387
2512
  return;
2388
2513
  }
2389
- pending.push(item);
2514
+ pending2.push(item);
2390
2515
  this.scheduleDeferredExitPopup(item);
2391
2516
  });
2392
- this.setDeferredExitQueue(pending);
2517
+ this.setDeferredExitQueue(pending2);
2393
2518
  }
2394
2519
  scheduleDeferredExitPopup(item) {
2395
2520
  if (typeof window === "undefined") return;
@@ -2462,7 +2587,7 @@ var DeepdotsPopups = class {
2462
2587
  async fetchPopupsFromServer() {
2463
2588
  const apiKey = this.config?.apiKey;
2464
2589
  const baseUrl = this.baseUrl;
2465
- const userId = this.config?.userId;
2590
+ const userId = this.tracking?.getUserId() ?? this.config?.userId;
2466
2591
  if (!apiKey || !baseUrl) {
2467
2592
  this.log("Missing apiKey or baseUrl. Skipping popups fetch.");
2468
2593
  return [];
@@ -2606,7 +2731,7 @@ var DeepdotsPopups = class {
2606
2731
  }
2607
2732
  if (!this.popupContainer) return;
2608
2733
  const container = this.popupContainer;
2609
- void import("./renderPopup-HHQMAW3G.mjs").then(({ renderPopup }) => {
2734
+ void import("./renderPopup-5QXL6EJW.mjs").then(({ renderPopup }) => {
2610
2735
  renderPopup(
2611
2736
  container,
2612
2737
  surveyId,
@@ -2861,8 +2986,10 @@ export {
2861
2986
  MAX_TRACKED_MESSAGES,
2862
2987
  MESSAGE_CHANNELS,
2863
2988
  MessageGuard,
2989
+ NATIVE_READY_GRACE_MS,
2864
2990
  NoopPopupRenderer,
2865
2991
  POPUP_TRIGGER_CONDITION_STATUSES,
2992
+ REVEAL_TIMEOUT_MS,
2866
2993
  RTL_LOCALES,
2867
2994
  ReactNativePopupRenderer,
2868
2995
  SUPPORTED_LANGUAGES,