@magicfeedback/popup-sdk 1.7.1 → 1.8.0

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.js CHANGED
@@ -466,6 +466,82 @@ var init_labels = __esm({
466
466
  }
467
467
  });
468
468
 
469
+ // src/ui/reveal.ts
470
+ function revealWhenPainted(root, reveal) {
471
+ const pending2 = Array.from(root.querySelectorAll("img")).filter((img) => !img.complete);
472
+ if (!pending2.length) {
473
+ reveal();
474
+ return;
475
+ }
476
+ let left = pending2.length;
477
+ const onSettled = () => {
478
+ left -= 1;
479
+ if (left <= 0) reveal();
480
+ };
481
+ pending2.forEach((img) => {
482
+ img.addEventListener("load", onSettled, { once: true });
483
+ img.addEventListener("error", onSettled, { once: true });
484
+ });
485
+ }
486
+ var REVEAL_TIMEOUT_MS;
487
+ var init_reveal = __esm({
488
+ "src/ui/reveal.ts"() {
489
+ "use strict";
490
+ REVEAL_TIMEOUT_MS = 1200;
491
+ }
492
+ });
493
+
494
+ // src/ui/surveyPalette.ts
495
+ function resolveSurveyPrimaryColor(style) {
496
+ for (const candidate of [style?.primaryColor, style?.buttonPrimaryColor]) {
497
+ const value = (candidate ?? "").trim();
498
+ if (HEX_COLOR.test(value)) return value;
499
+ }
500
+ return null;
501
+ }
502
+ function surveyPrimaryVars(color) {
503
+ return [
504
+ ["--mf-primary", color],
505
+ ["--mf-primary-hover", `color-mix(in srgb, ${color} 85%, black)`],
506
+ ["--mf-primary-light", `color-mix(in srgb, ${color} 15%, white)`],
507
+ ["--mf-primary-border", `color-mix(in srgb, ${color} 35%, transparent)`],
508
+ ["--mf-border-focus", color]
509
+ ];
510
+ }
511
+ function applySurveyPrimaryColor(el, style) {
512
+ const color = resolveSurveyPrimaryColor(style);
513
+ if (!color) return;
514
+ for (const [name, value] of surveyPrimaryVars(color)) {
515
+ el.style.setProperty(name, value);
516
+ }
517
+ }
518
+ var HEX_COLOR, PRIMARY_COLOR_JS;
519
+ var init_surveyPalette = __esm({
520
+ "src/ui/surveyPalette.ts"() {
521
+ "use strict";
522
+ HEX_COLOR = /^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
523
+ PRIMARY_COLOR_JS = `
524
+ function ddResolveSurveyPrimaryColor(style){
525
+ var hex=/^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
526
+ var candidates=[style&&style.primaryColor, style&&style.buttonPrimaryColor];
527
+ for(var i=0;i<candidates.length;i++){
528
+ var value=(candidates[i]||'').trim();
529
+ if(hex.test(value)){ return value; }
530
+ }
531
+ return null;
532
+ }
533
+ function ddApplySurveyPrimaryColor(el, style){
534
+ var color=ddResolveSurveyPrimaryColor(style);
535
+ if(!color||!el){ return; }
536
+ el.style.setProperty('--mf-primary', color);
537
+ el.style.setProperty('--mf-primary-hover', 'color-mix(in srgb, '+color+' 85%, black)');
538
+ el.style.setProperty('--mf-primary-light', 'color-mix(in srgb, '+color+' 15%, white)');
539
+ el.style.setProperty('--mf-primary-border', 'color-mix(in srgb, '+color+' 35%, transparent)');
540
+ el.style.setProperty('--mf-border-focus', color);
541
+ }`;
542
+ }
543
+ });
544
+
469
545
  // src/assets/style.css
470
546
  var style_default;
471
547
  var init_style = __esm({
@@ -2355,9 +2431,9 @@ var init_logo = __esm({
2355
2431
  }
2356
2432
  });
2357
2433
 
2358
- // ../deepdots-popup-sdk/node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js
2434
+ // node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js
2359
2435
  var require_magicfeedback_sdk_browser = __commonJS({
2360
- "../deepdots-popup-sdk/node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js"(exports, module2) {
2436
+ "node_modules/@magicfeedback/native/dist/magicfeedback-sdk.browser.js"(exports, module2) {
2361
2437
  "use strict";
2362
2438
  !(function(e, t) {
2363
2439
  "object" == typeof exports && "object" == typeof module2 ? module2.exports = t() : "function" == typeof define && define.amd ? define([], t) : "object" == typeof exports ? exports.magicfeedback = t() : e.magicfeedback = t();
@@ -4755,6 +4831,8 @@ var require_magicfeedback_sdk_browser = __commonJS({
4755
4831
  // src/ui/renderPopup.ts
4756
4832
  var renderPopup_exports = {};
4757
4833
  __export(renderPopup_exports, {
4834
+ REVEAL_TIMEOUT_MS: () => REVEAL_TIMEOUT_MS,
4835
+ SURVEY_ALIGNMENT_STYLE_ID: () => SURVEY_ALIGNMENT_STYLE_ID,
4758
4836
  renderPopup: () => renderPopup
4759
4837
  });
4760
4838
  function ensureMagicFeedbackStyles(_popup) {
@@ -4765,6 +4843,17 @@ function ensureMagicFeedbackStyles(_popup) {
4765
4843
  style.textContent = style_default;
4766
4844
  document.head.appendChild(style);
4767
4845
  }
4846
+ function ensureSurveyAlignmentStyles() {
4847
+ if (document.getElementById(SURVEY_ALIGNMENT_STYLE_ID)) return;
4848
+ const style = document.createElement("style");
4849
+ style.id = SURVEY_ALIGNMENT_STYLE_ID;
4850
+ style.textContent = `
4851
+ .deepdots-popup .magicfeedback-container,
4852
+ .deepdots-popup .magicfeedback-form,
4853
+ .deepdots-popup .magicfeedback-div { padding-left: 0; padding-right: 0; }
4854
+ `;
4855
+ document.head.appendChild(style);
4856
+ }
4768
4857
  function ensureSpinnerStyles(_popup) {
4769
4858
  if (document.getElementById("deepdots-spinner-styles")) return;
4770
4859
  const style = document.createElement("style");
@@ -5017,6 +5106,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5017
5106
  progressFollowUp.style.display = p.followup ? "inline-block" : "none";
5018
5107
  }
5019
5108
  ensureMagicFeedbackStyles(popup);
5109
+ ensureSurveyAlignmentStyles();
5020
5110
  ensureSpinnerStyles(popup);
5021
5111
  ensureResponsiveStyles(popup);
5022
5112
  ensureCustomCss(options?.surveyCss);
@@ -5075,7 +5165,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5075
5165
  border: none;
5076
5166
  min-height: 44px;
5077
5167
  padding: 12px 24px;
5078
- border-radius: 6px;
5168
+ border-radius: 999px; /* p\xEDldora, como el Button de Material3 en KMP */
5079
5169
  cursor: pointer;
5080
5170
  font-size: 15px;
5081
5171
  font-weight: 600;
@@ -5105,7 +5195,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5105
5195
  border: none;
5106
5196
  min-height: 44px;
5107
5197
  padding: 12px 24px;
5108
- border-radius: 6px;
5198
+ border-radius: 999px; /* p\xEDldora, como el Button de Material3 en KMP */
5109
5199
  cursor: pointer;
5110
5200
  font-size: 15px;
5111
5201
  font-weight: 600;
@@ -5132,7 +5222,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5132
5222
  border: none;
5133
5223
  min-height: 44px;
5134
5224
  padding: 12px 24px;
5135
- border-radius: 6px;
5225
+ border-radius: 999px; /* p\xEDldora, como el Button de Material3 en KMP */
5136
5226
  cursor: pointer;
5137
5227
  font-size: 15px;
5138
5228
  font-weight: 600;
@@ -5163,7 +5253,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5163
5253
  border: none;
5164
5254
  min-height: 44px;
5165
5255
  padding: 12px 24px;
5166
- border-radius: 6px;
5256
+ border-radius: 999px; /* p\xEDldora, como el Button de Material3 en KMP */
5167
5257
  cursor: pointer;
5168
5258
  font-size: 15px;
5169
5259
  font-weight: 600;
@@ -5265,14 +5355,35 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5265
5355
  container.innerHTML = "";
5266
5356
  container.appendChild(popup);
5267
5357
  container.style.display = "flex";
5358
+ container.style.visibility = "hidden";
5359
+ container.style.pointerEvents = "none";
5268
5360
  container.style.justifyContent = pos.justifyContent;
5269
5361
  container.style.alignItems = pos.alignItems;
5270
5362
  container.style.background = pos.background;
5271
5363
  if (pos.padding) container.style.padding = pos.padding;
5364
+ let revealed = false;
5365
+ let revealTimer = null;
5366
+ function reveal() {
5367
+ if (revealed) return;
5368
+ revealed = true;
5369
+ if (revealTimer !== null) {
5370
+ clearTimeout(revealTimer);
5371
+ revealTimer = null;
5372
+ }
5373
+ if (!container.contains(popup)) return;
5374
+ container.style.visibility = "visible";
5375
+ container.style.pointerEvents = "";
5376
+ }
5377
+ function revealWhenPainted2() {
5378
+ if (revealed) return;
5379
+ revealWhenPainted(popup, reveal);
5380
+ }
5381
+ revealTimer = setTimeout(reveal, REVEAL_TIMEOUT_MS);
5272
5382
  function setLoading(isLoading) {
5273
5383
  spinnerEl.style.display = isLoading ? "flex" : "none";
5274
5384
  if (!isLoading) {
5275
5385
  formHost.style.visibility = "visible";
5386
+ revealWhenPainted2();
5276
5387
  }
5277
5388
  footer.style.display = isLoading ? "none" : "flex";
5278
5389
  backButton.disabled = isLoading;
@@ -5335,6 +5446,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5335
5446
  if (s.contentAlign) {
5336
5447
  main.style.justifyContent = s.contentAlign === "center" ? "center" : "flex-start";
5337
5448
  }
5449
+ applySurveyPrimaryColor(popup, s);
5338
5450
  if (s.buttonPrimaryColor) {
5339
5451
  submitButton.style.background = s.buttonPrimaryColor;
5340
5452
  submitButton.style.border = "none";
@@ -5416,17 +5528,21 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5416
5528
  setLoading(false);
5417
5529
  }
5418
5530
  }
5419
- var import_native;
5531
+ var import_native, SURVEY_ALIGNMENT_STYLE_ID;
5420
5532
  var init_renderPopup = __esm({
5421
5533
  "src/ui/renderPopup.ts"() {
5422
5534
  "use strict";
5423
5535
  init_tracking_manager();
5424
5536
  init_font();
5425
5537
  init_logo();
5538
+ init_surveyPalette();
5426
5539
  init_logger();
5427
5540
  init_labels();
5541
+ init_reveal();
5428
5542
  import_native = __toESM(require_magicfeedback_sdk_browser());
5429
5543
  init_style();
5544
+ init_reveal();
5545
+ SURVEY_ALIGNMENT_STYLE_ID = "deepdots-survey-alignment";
5430
5546
  }
5431
5547
  });
5432
5548
 
@@ -5442,8 +5558,10 @@ __export(index_exports, {
5442
5558
  MAX_TRACKED_MESSAGES: () => MAX_TRACKED_MESSAGES,
5443
5559
  MESSAGE_CHANNELS: () => MESSAGE_CHANNELS,
5444
5560
  MessageGuard: () => MessageGuard,
5561
+ NATIVE_READY_GRACE_MS: () => NATIVE_READY_GRACE_MS,
5445
5562
  NoopPopupRenderer: () => NoopPopupRenderer,
5446
5563
  POPUP_TRIGGER_CONDITION_STATUSES: () => POPUP_TRIGGER_CONDITION_STATUSES,
5564
+ REVEAL_TIMEOUT_MS: () => REVEAL_TIMEOUT_MS,
5447
5565
  RTL_LOCALES: () => RTL_LOCALES,
5448
5566
  ReactNativePopupRenderer: () => ReactNativePopupRenderer,
5449
5567
  SUPPORTED_LANGUAGES: () => SUPPORTED_LANGUAGES,
@@ -5658,7 +5776,33 @@ init_tracking_manager();
5658
5776
  // src/ui/surveyHtml.ts
5659
5777
  init_font();
5660
5778
  init_labels();
5779
+ init_reveal();
5780
+ init_surveyPalette();
5661
5781
  init_style();
5782
+ var REVEAL_JS = `
5783
+ function ddCreateReveal(popup, onReveal, timeoutMs){
5784
+ var revealed=false, timer=null;
5785
+ function reveal(){
5786
+ if(revealed){ return; }
5787
+ revealed=true;
5788
+ if(timer){ clearTimeout(timer); timer=null; }
5789
+ onReveal();
5790
+ }
5791
+ function revealWhenPainted(){
5792
+ if(revealed){ return; }
5793
+ var imgs=popup.querySelectorAll('img'), pending=[];
5794
+ for(var i=0;i<imgs.length;i++){ if(!imgs[i].complete){ pending.push(imgs[i]); } }
5795
+ if(!pending.length){ reveal(); return; }
5796
+ var left=pending.length;
5797
+ function onSettled(){ left--; if(left<=0){ reveal(); } }
5798
+ for(var j=0;j<pending.length;j++){
5799
+ pending[j].addEventListener('load', onSettled);
5800
+ pending[j].addEventListener('error', onSettled);
5801
+ }
5802
+ }
5803
+ timer=setTimeout(reveal, timeoutMs);
5804
+ return { reveal: reveal, whenPainted: revealWhenPainted };
5805
+ }`;
5662
5806
  var POSITION_MAP = {
5663
5807
  center: { justifyContent: "center", alignItems: "center", padding: "0", background: "rgba(0,0,0,0.5)" },
5664
5808
  bottom: { justifyContent: "center", alignItems: "flex-end", padding: "16px", background: "transparent" },
@@ -5732,7 +5876,13 @@ function buildSurveyHtml(opts) {
5732
5876
  ${style_default}
5733
5877
  ${fontFaceCss}
5734
5878
  html,body{margin:0;padding:0;height:100%;font-family:${fontFamilyValue}}
5735
- body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:${bodyBg};padding:${bodyPadding};box-sizing:border-box}
5879
+ /* Apertura diferida: ni la tarjeta ni el velo se pintan hasta que el survey est\xE1 listo
5880
+ (\`dd-ready\`). Si el fondo entrara antes, en RN se ver\xEDa el velo oscuro varios cientos de ms
5881
+ antes que el contenido, que es la misma mala sensaci\xF3n que el spinner. */
5882
+ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${pos.alignItems};background:transparent;padding:${bodyPadding};box-sizing:border-box}
5883
+ body.dd-ready{background:${bodyBg}}
5884
+ #dd-popup{visibility:hidden}
5885
+ body.dd-ready #dd-popup{visibility:visible}
5736
5886
  .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}
5737
5887
  .deepdots-popup-header{display:flex;justify-content:space-between;align-items:center;gap:12px;width:100%;flex:0 0 auto}
5738
5888
  /* Neutraliza la regla \`.deepdots-popup h2\` del CSS del survey (uppercase, centrado y
@@ -5766,6 +5916,13 @@ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${
5766
5916
  #dd-form-wrapper{width:100%;flex:1 1 auto}
5767
5917
  #mf{width:100%;box-sizing:border-box;visibility:hidden}
5768
5918
  .deepdots-popup-main *{max-width:100%;box-sizing:border-box}
5919
+ /* El margen lateral lo pone la tarjeta: el CSS del survey lo a\xF1ade otra vez en el contenedor, en
5920
+ el formulario y en el bloque de cada pregunta, y eso deja el enunciado y las opciones m\xE1s
5921
+ adentro que el logo, el t\xEDtulo y la barra de progreso. Solo el lateral; el vertical separa las
5922
+ preguntas. Mismas reglas que en el popup DOM (renderPopup.ts). */
5923
+ .deepdots-popup .magicfeedback-container,
5924
+ .deepdots-popup .magicfeedback-form,
5925
+ .deepdots-popup .magicfeedback-div{padding-left:0;padding-right:0}
5769
5926
  #dd-logo{max-height:40px;max-width:100%;object-fit:contain;display:block;margin:12px 0 0 0}
5770
5927
  .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}
5771
5928
  /* Pantalla final: el HTML del editor de la plataforma (imagen + texto), centrado. */
@@ -5773,7 +5930,7 @@ body{display:${bodyDisplay};justify-content:${pos.justifyContent};align-items:${
5773
5930
  .deepdots-success img{max-width:100%;height:auto;margin:0 auto 16px auto;display:block}
5774
5931
  .deepdots-success p{margin:0;font-size:16px;font-weight:600;line-height:1.4}
5775
5932
  .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}
5776
- .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}
5933
+ .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}
5777
5934
  /* Secundario como bot\xF3n de texto: sin borde ni fondo, para que el primario sea la \xFAnica CTA. */
5778
5935
  #dd-back{background:transparent;color:${textMuted};border:none}
5779
5936
  #dd-start,#dd-complete,#dd-submit{background:#1E293B;color:#fff;border:none}
@@ -5998,9 +6155,23 @@ ${customCss}
5998
6155
  }
5999
6156
  }
6000
6157
 
6158
+ ${PRIMARY_COLOR_JS}
6159
+ ${REVEAL_JS}
6160
+ // El popup se ense\xF1a cuando el survey est\xE1 pintado; \`ready\` deja al host abrir su propio
6161
+ // Modal en ese momento en vez de montar un WebView en blanco.
6162
+ var ddReveal=ddCreateReveal(popup, function(){
6163
+ document.body.classList.add('dd-ready');
6164
+ emitJSON('ready');
6165
+ }, ${REVEAL_TIMEOUT_MS});
6166
+
6001
6167
  function setLoading(isLoading){
6002
6168
  spinner.style.display=isLoading?'flex':'none';
6003
- if(!isLoading){ formHost.style.visibility='visible'; }
6169
+ if(!isLoading){
6170
+ formHost.style.visibility='visible';
6171
+ // Fin de la carga, sea por survey cargado o por error. Entre p\xE1ginas es no-op: la
6172
+ // revelaci\xF3n solo act\xFAa la primera vez.
6173
+ ddReveal.whenPainted();
6174
+ }
6004
6175
  footer.style.display=isLoading?'none':'flex';
6005
6176
  backBtn.disabled=isLoading; startBtn.disabled=isLoading; completeBtn.disabled=isLoading; submitBtn.disabled=isLoading;
6006
6177
  if(isLoading){ updateButtons(null); }
@@ -6043,6 +6214,10 @@ ${customCss}
6043
6214
  if(s.loadingBarColor){ progressBar.style.background=s.loadingBarColor; progressFollowUp.style.background=s.loadingBarColor; }
6044
6215
  if(chromeOn && s.boxBackgroundColor){ popup.style.background=s.boxBackgroundColor; }
6045
6216
  if(s.contentAlign){ main.style.justifyContent = s.contentAlign==='center' ? 'center' : 'flex-start'; }
6217
+ // El survey pinta sus controles con --mf-primary, que sale de primaryColor; si la
6218
+ // integraci\xF3n solo configura el del bot\xF3n, el popup mezclar\xEDa el color de la marca
6219
+ // con el gris azulado por defecto del paquete.
6220
+ ddApplySurveyPrimaryColor(popup, s);
6046
6221
  if(s.buttonPrimaryColor){
6047
6222
  submitBtn.style.background=s.buttonPrimaryColor; submitBtn.style.border='none'; submitBtn.style.color='#fff';
6048
6223
  startBtn.style.background=s.buttonPrimaryColor; startBtn.style.border='none'; startBtn.style.color='#fff';
@@ -6134,7 +6309,9 @@ ${customCss}
6134
6309
  }
6135
6310
 
6136
6311
  // src/platform/react-native-renderer.ts
6312
+ init_reveal();
6137
6313
  init_logger();
6314
+ var NATIVE_READY_GRACE_MS = 500;
6138
6315
  var ReactNativePopupRenderer = class {
6139
6316
  constructor(options = {}) {
6140
6317
  this.options = options;
@@ -6142,6 +6319,8 @@ var ReactNativePopupRenderer = class {
6142
6319
  this.onCloseFn = null;
6143
6320
  this.currentSurveyId = null;
6144
6321
  this.partialEmitted = false;
6322
+ this.readyEmitted = false;
6323
+ this.readyTimer = null;
6145
6324
  }
6146
6325
  init() {
6147
6326
  }
@@ -6150,6 +6329,8 @@ var ReactNativePopupRenderer = class {
6150
6329
  this.onCloseFn = onClose;
6151
6330
  this.currentSurveyId = surveyId;
6152
6331
  this.partialEmitted = false;
6332
+ this.readyEmitted = false;
6333
+ this.armReadyFallback(surveyId);
6153
6334
  const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
6154
6335
  const html = buildSurveyHtml({
6155
6336
  surveyId,
@@ -6177,9 +6358,32 @@ var ReactNativePopupRenderer = class {
6177
6358
  }
6178
6359
  }
6179
6360
  hide() {
6361
+ this.clearReadyFallback();
6180
6362
  this.options.onHide?.();
6181
6363
  this.currentSurveyId = null;
6182
6364
  }
6365
+ /** Respaldo del aviso de listo por si el WebView no llega a ejecutar el HTML. */
6366
+ armReadyFallback(surveyId) {
6367
+ this.clearReadyFallback();
6368
+ if (!this.options.onReady) return;
6369
+ this.readyTimer = setTimeout(() => {
6370
+ this.readyTimer = null;
6371
+ this.emitReady(surveyId);
6372
+ }, REVEAL_TIMEOUT_MS + NATIVE_READY_GRACE_MS);
6373
+ }
6374
+ clearReadyFallback() {
6375
+ if (this.readyTimer !== null) {
6376
+ clearTimeout(this.readyTimer);
6377
+ this.readyTimer = null;
6378
+ }
6379
+ }
6380
+ /** Idempotente: el WebView puede avisar a la vez que vence el respaldo. */
6381
+ emitReady(surveyId) {
6382
+ if (this.readyEmitted) return;
6383
+ this.readyEmitted = true;
6384
+ this.clearReadyFallback();
6385
+ this.options.onReady?.({ surveyId });
6386
+ }
6183
6387
  /**
6184
6388
  * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
6185
6389
  * eventos del SDK: primera interacción → `popup_clicked` (PARTIAL), completado →
@@ -6195,6 +6399,9 @@ var ReactNativePopupRenderer = class {
6195
6399
  name = raw;
6196
6400
  }
6197
6401
  switch (name) {
6402
+ case "ready":
6403
+ this.emitReady(surveyId);
6404
+ break;
6198
6405
  case "loaded":
6199
6406
  case "before_submit":
6200
6407
  case "after_submit":
@@ -6222,6 +6429,33 @@ function createReactNativeRenderer() {
6222
6429
  return new ReactNativePopupRenderer();
6223
6430
  }
6224
6431
 
6432
+ // src/ui/preload.ts
6433
+ var defaultLoader = () => Promise.resolve().then(() => (init_renderPopup(), renderPopup_exports));
6434
+ var pending = null;
6435
+ function preloadRenderPopup(loader = defaultLoader) {
6436
+ if (pending) return pending;
6437
+ try {
6438
+ pending = loader().then(() => void 0).catch(() => {
6439
+ pending = null;
6440
+ });
6441
+ } catch {
6442
+ pending = null;
6443
+ return Promise.resolve();
6444
+ }
6445
+ return pending;
6446
+ }
6447
+ function schedulePreloadRenderPopup(loader) {
6448
+ const run = () => {
6449
+ void preloadRenderPopup(loader);
6450
+ };
6451
+ const idle = globalThis.requestIdleCallback;
6452
+ if (typeof idle === "function") {
6453
+ idle(run, { timeout: 2e3 });
6454
+ return;
6455
+ }
6456
+ setTimeout(run, 0);
6457
+ }
6458
+
6225
6459
  // src/platform/renderer.ts
6226
6460
  var NoopPopupRenderer = class {
6227
6461
  show() {
@@ -6256,6 +6490,13 @@ var BrowserPopupRenderer = class {
6256
6490
  document.body.appendChild(this.container);
6257
6491
  }
6258
6492
  }
6493
+ /**
6494
+ * Descarga el chunk de `renderPopup` antes de que ningún trigger dispare, para que abrir el
6495
+ * popup no empiece por esperar ~238 KB de JS.
6496
+ */
6497
+ preload() {
6498
+ schedulePreloadRenderPopup();
6499
+ }
6259
6500
  show(surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId, _renderChrome, options) {
6260
6501
  if (this.visible) return;
6261
6502
  if (!this.container || !document.body.contains(this.container)) this.init();
@@ -6270,6 +6511,8 @@ var BrowserPopupRenderer = class {
6270
6511
  this.visible = false;
6271
6512
  if (this.container) {
6272
6513
  this.container.style.display = "none";
6514
+ this.container.style.visibility = "";
6515
+ this.container.style.pointerEvents = "";
6273
6516
  this.container.innerHTML = "";
6274
6517
  }
6275
6518
  }
@@ -7346,6 +7589,7 @@ var DeepdotsPopups = class {
7346
7589
  this.popupDefinitions = this.validatePopupDefinitions(defs);
7347
7590
  this.popupsLoaded = true;
7348
7591
  this.log("Popups loaded from API", this.popupDefinitions);
7592
+ if (this.popupDefinitions.length) this.renderer.preload?.();
7349
7593
  this.configureTriggersFromDefinitions();
7350
7594
  this.processDeferredExitQueue();
7351
7595
  if (this.pendingAutoLaunch) {
@@ -7704,6 +7948,9 @@ var DeepdotsPopups = class {
7704
7948
  this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
7705
7949
  }
7706
7950
  shouldShow(def, pathUrl, skipPathCheck = false) {
7951
+ if (this.isPathExcluded(def, pathUrl)) {
7952
+ return false;
7953
+ }
7707
7954
  if (!skipPathCheck && !this.matchesSegmentsPath(def, pathUrl)) {
7708
7955
  return false;
7709
7956
  }
@@ -7734,40 +7981,85 @@ var DeepdotsPopups = class {
7734
7981
  }
7735
7982
  return matches;
7736
7983
  }
7984
+ /** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */
7737
7985
  matchesSegmentsPath(def, pathUrl) {
7738
7986
  const paths = def.segments?.path;
7739
- if (!paths || paths.length === 0) return true;
7740
- if (typeof window === "undefined" || !window.location) {
7987
+ if (!Array.isArray(paths) || paths.length === 0) return true;
7988
+ const location = this.resolveCurrentLocation(pathUrl);
7989
+ if (!location) {
7741
7990
  this.debug("No window.location available for path comparison", { popupId: def.id, paths });
7742
7991
  return true;
7743
7992
  }
7744
- const normalizedHref = this.normalizeUrl(pathUrl || window.location.href || "");
7745
- const currentUrl = this.safeParseUrl(normalizedHref);
7746
- const normalizedPath = this.normalizeUrl(currentUrl?.pathname || window.location.pathname || "");
7747
7993
  const matches = paths.some((rawCandidate) => {
7748
- const candidate = this.normalizeUrl(rawCandidate);
7749
- let match = false;
7750
- if (candidate.startsWith("http://") || candidate.startsWith("https://")) {
7751
- match = normalizedHref === candidate;
7752
- } else if (candidate.startsWith("/")) {
7753
- match = normalizedHref.includes(candidate);
7754
- } else {
7755
- match = normalizedPath === candidate;
7756
- }
7994
+ const match = this.matchesPathCandidate(rawCandidate, location);
7757
7995
  this.debug("Path comparison", {
7758
7996
  popupId: def.id,
7759
- candidate,
7760
- currentPath: normalizedPath,
7761
- currentHref: normalizedHref,
7997
+ candidate: rawCandidate,
7998
+ currentPath: location.path,
7999
+ currentHref: location.href,
7762
8000
  match
7763
8001
  });
7764
8002
  return match;
7765
8003
  });
7766
8004
  if (!matches) {
7767
- this.debug("No path match for popup", { popupId: def.id, paths, currentPath: normalizedPath });
8005
+ this.debug("No path match for popup", { popupId: def.id, paths, currentPath: location.path });
7768
8006
  }
7769
8007
  return matches;
7770
8008
  }
8009
+ /**
8010
+ * `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre
8011
+ * `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el
8012
+ * carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).
8013
+ */
8014
+ isPathExcluded(def, pathUrl) {
8015
+ const excluded = def.segments?.excludedPaths;
8016
+ if (!Array.isArray(excluded) || excluded.length === 0) return false;
8017
+ const location = this.resolveCurrentLocation(pathUrl);
8018
+ if (!location) {
8019
+ this.debug("No window.location available for excluded path comparison", { popupId: def.id, excluded });
8020
+ return false;
8021
+ }
8022
+ const hit = excluded.find((rawCandidate) => this.matchesPathCandidate(rawCandidate, location));
8023
+ if (hit !== void 0) {
8024
+ this.debug("Popup excluded by path", {
8025
+ popupId: def.id,
8026
+ candidate: hit,
8027
+ currentPath: location.path,
8028
+ currentHref: location.href
8029
+ });
8030
+ return true;
8031
+ }
8032
+ return false;
8033
+ }
8034
+ /**
8035
+ * Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta
8036
+ * actual. Mismas reglas para incluir y para excluir:
8037
+ * - URL absoluta → href completo exacto
8038
+ * - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)
8039
+ * - resto → pathname exacto
8040
+ * Ignora entradas que no sean string: el backend puede colar un `null` en la lista
8041
+ * y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con
8042
+ * `segments.lang`).
8043
+ */
8044
+ matchesPathCandidate(rawCandidate, location) {
8045
+ if (typeof rawCandidate !== "string") return false;
8046
+ const candidate = this.normalizeUrl(rawCandidate);
8047
+ if (!candidate) return false;
8048
+ if (candidate.startsWith("http://") || candidate.startsWith("https://")) {
8049
+ return location.href === candidate;
8050
+ }
8051
+ if (candidate.startsWith("/")) {
8052
+ return location.href.includes(candidate);
8053
+ }
8054
+ return location.path === candidate;
8055
+ }
8056
+ /** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */
8057
+ resolveCurrentLocation(pathUrl) {
8058
+ if (typeof window === "undefined" || !window.location) return null;
8059
+ const href = this.normalizeUrl(pathUrl || window.location.href || "");
8060
+ const parsed = this.safeParseUrl(href);
8061
+ return { href, path: this.normalizeUrl(parsed?.pathname || window.location.pathname || "") };
8062
+ }
7771
8063
  evaluateCooldown(def, condition) {
7772
8064
  switch (condition.answered) {
7773
8065
  case "SHOWED":
@@ -7833,7 +8125,7 @@ var DeepdotsPopups = class {
7833
8125
  processDeferredExitQueue() {
7834
8126
  const queue = this.getDeferredExitQueue();
7835
8127
  if (!queue.length) return;
7836
- const pending = [];
8128
+ const pending2 = [];
7837
8129
  const now = Date.now();
7838
8130
  queue.forEach((item) => {
7839
8131
  const def = this.popupDefinitions.find((popup) => popup.id === item.id && popup.surveyId === item.surveyId);
@@ -7844,10 +8136,10 @@ var DeepdotsPopups = class {
7844
8136
  this.tryShowDeferredExitPopup(item);
7845
8137
  return;
7846
8138
  }
7847
- pending.push(item);
8139
+ pending2.push(item);
7848
8140
  this.scheduleDeferredExitPopup(item);
7849
8141
  });
7850
- this.setDeferredExitQueue(pending);
8142
+ this.setDeferredExitQueue(pending2);
7851
8143
  }
7852
8144
  scheduleDeferredExitPopup(item) {
7853
8145
  if (typeof window === "undefined") return;
@@ -7920,7 +8212,7 @@ var DeepdotsPopups = class {
7920
8212
  async fetchPopupsFromServer() {
7921
8213
  const apiKey = this.config?.apiKey;
7922
8214
  const baseUrl = this.baseUrl;
7923
- const userId = this.config?.userId;
8215
+ const userId = this.tracking?.getUserId() ?? this.config?.userId;
7924
8216
  if (!apiKey || !baseUrl) {
7925
8217
  this.log("Missing apiKey or baseUrl. Skipping popups fetch.");
7926
8218
  return [];
@@ -8273,6 +8565,7 @@ var DeepdotsPopups = class {
8273
8565
 
8274
8566
  // src/index.ts
8275
8567
  init_tracking_manager();
8568
+ init_reveal();
8276
8569
  init_labels();
8277
8570
 
8278
8571
  // src/react-native/setup.ts