@magicfeedback/popup-sdk 1.15.0 → 1.16.1

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
@@ -256,7 +256,8 @@ var init_labels = __esm({
256
256
  closeAria: "Close popup",
257
257
  loadingAria: "Loading survey",
258
258
  errorRequired: "Please answer the required question to continue.",
259
- errorSubmit: "An error occurred while submitting. Please try again or close the popup."
259
+ errorSubmit: "An error occurred while submitting. Please try again or close the popup.",
260
+ errorLoad: "We couldn't load this survey."
260
261
  };
261
262
  ES = {
262
263
  accept: "Enviar",
@@ -270,7 +271,8 @@ var init_labels = __esm({
270
271
  closeAria: "Cerrar ventana emergente",
271
272
  loadingAria: "Cargando encuesta",
272
273
  errorRequired: "Responde la pregunta obligatoria para continuar.",
273
- errorSubmit: "Se produjo un error al enviar. Int\xE9ntalo de nuevo o cierra la ventana."
274
+ errorSubmit: "Se produjo un error al enviar. Int\xE9ntalo de nuevo o cierra la ventana.",
275
+ errorLoad: "No se ha podido cargar la encuesta."
274
276
  };
275
277
  DA = {
276
278
  accept: "Send",
@@ -284,7 +286,8 @@ var init_labels = __esm({
284
286
  closeAria: "Luk pop op",
285
287
  loadingAria: "Indl\xE6ser unders\xF8gelse",
286
288
  errorRequired: "Besvar venligst det obligatoriske sp\xF8rgsm\xE5l for at forts\xE6tte.",
287
- errorSubmit: "Der opstod en fejl under afsendelsen. Pr\xF8v igen, eller luk pop op-vinduet."
289
+ errorSubmit: "Der opstod en fejl under afsendelsen. Pr\xF8v igen, eller luk pop op-vinduet.",
290
+ errorLoad: "Unders\xF8gelsen kunne ikke indl\xE6ses."
288
291
  };
289
292
  NO = {
290
293
  accept: "Send",
@@ -298,7 +301,8 @@ var init_labels = __esm({
298
301
  closeAria: "Lukk popup",
299
302
  loadingAria: "Laster unders\xF8kelse",
300
303
  errorRequired: "Svar p\xE5 det obligatoriske sp\xF8rsm\xE5let for \xE5 fortsette.",
301
- errorSubmit: "Det oppstod en feil under innsendingen. Pr\xF8v igjen, eller lukk popupen."
304
+ errorSubmit: "Det oppstod en feil under innsendingen. Pr\xF8v igjen, eller lukk popupen.",
305
+ errorLoad: "Unders\xF8kelsen kunne ikke lastes inn."
302
306
  };
303
307
  SV = {
304
308
  accept: "Skicka",
@@ -312,7 +316,8 @@ var init_labels = __esm({
312
316
  closeAria: "St\xE4ng popup",
313
317
  loadingAria: "Laddar unders\xF6kning",
314
318
  errorRequired: "Svara p\xE5 den obligatoriska fr\xE5gan f\xF6r att forts\xE4tta.",
315
- errorSubmit: "Ett fel uppstod vid inskickningen. F\xF6rs\xF6k igen eller st\xE4ng popupen."
319
+ errorSubmit: "Ett fel uppstod vid inskickningen. F\xF6rs\xF6k igen eller st\xE4ng popupen.",
320
+ errorLoad: "Enk\xE4ten kunde inte laddas."
316
321
  };
317
322
  FI = {
318
323
  accept: "L\xE4het\xE4",
@@ -326,7 +331,8 @@ var init_labels = __esm({
326
331
  closeAria: "Sulje ponnahdusikkuna",
327
332
  loadingAria: "Ladataan kysely\xE4",
328
333
  errorRequired: "Vastaa pakolliseen kysymykseen jatkaaksesi.",
329
- errorSubmit: "L\xE4hetyksess\xE4 tapahtui virhe. Yrit\xE4 uudelleen tai sulje ponnahdusikkuna."
334
+ errorSubmit: "L\xE4hetyksess\xE4 tapahtui virhe. Yrit\xE4 uudelleen tai sulje ponnahdusikkuna.",
335
+ errorLoad: "Kysely\xE4 ei voitu ladata."
330
336
  };
331
337
  DE = {
332
338
  accept: "Senden",
@@ -340,7 +346,8 @@ var init_labels = __esm({
340
346
  closeAria: "Popup schlie\xDFen",
341
347
  loadingAria: "Umfrage wird geladen",
342
348
  errorRequired: "Bitte beantworte die Pflichtfrage, um fortzufahren.",
343
- errorSubmit: "Beim Senden ist ein Fehler aufgetreten. Versuche es erneut oder schlie\xDFe das Popup."
349
+ errorSubmit: "Beim Senden ist ein Fehler aufgetreten. Versuche es erneut oder schlie\xDFe das Popup.",
350
+ errorLoad: "Die Umfrage konnte nicht geladen werden."
344
351
  };
345
352
  FR = {
346
353
  accept: "Envoyer",
@@ -354,7 +361,8 @@ var init_labels = __esm({
354
361
  closeAria: "Fermer la fen\xEAtre",
355
362
  loadingAria: "Chargement de l'enqu\xEAte",
356
363
  errorRequired: "Veuillez r\xE9pondre \xE0 la question obligatoire pour continuer.",
357
- errorSubmit: "Une erreur s'est produite lors de l'envoi. R\xE9essayez ou fermez la fen\xEAtre."
364
+ errorSubmit: "Une erreur s'est produite lors de l'envoi. R\xE9essayez ou fermez la fen\xEAtre.",
365
+ errorLoad: "Impossible de charger l'enqu\xEAte."
358
366
  };
359
367
  PT = {
360
368
  accept: "Enviar",
@@ -368,7 +376,8 @@ var init_labels = __esm({
368
376
  closeAria: "Fechar janela",
369
377
  loadingAria: "A carregar question\xE1rio",
370
378
  errorRequired: "Responda \xE0 pergunta obrigat\xF3ria para continuar.",
371
- errorSubmit: "Ocorreu um erro ao enviar. Tente novamente ou feche a janela."
379
+ errorSubmit: "Ocorreu um erro ao enviar. Tente novamente ou feche a janela.",
380
+ errorLoad: "N\xE3o foi poss\xEDvel carregar o inqu\xE9rito."
372
381
  };
373
382
  AR = {
374
383
  accept: "\u0625\u0631\u0633\u0627\u0644",
@@ -382,7 +391,8 @@ var init_labels = __esm({
382
391
  closeAria: "\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0646\u0627\u0641\u0630\u0629 \u0627\u0644\u0645\u0646\u0628\u062B\u0642\u0629",
383
392
  loadingAria: "\u062C\u0627\u0631\u064D \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0627\u0633\u062A\u0628\u064A\u0627\u0646",
384
393
  errorRequired: "\u064A\u0631\u062C\u0649 \u0627\u0644\u0625\u062C\u0627\u0628\u0629 \u0639\u0644\u0649 \u0627\u0644\u0633\u0624\u0627\u0644 \u0627\u0644\u0645\u0637\u0644\u0648\u0628 \u0644\u0644\u0645\u062A\u0627\u0628\u0639\u0629.",
385
- errorSubmit: "\u062D\u062F\u062B \u062E\u0637\u0623 \u0623\u062B\u0646\u0627\u0621 \u0627\u0644\u0625\u0631\u0633\u0627\u0644. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649 \u0623\u0648 \u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0646\u0627\u0641\u0630\u0629."
394
+ errorSubmit: "\u062D\u062F\u062B \u062E\u0637\u0623 \u0623\u062B\u0646\u0627\u0621 \u0627\u0644\u0625\u0631\u0633\u0627\u0644. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649 \u0623\u0648 \u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0646\u0627\u0641\u0630\u0629.",
395
+ errorLoad: "\u062A\u0639\u0630\u0651\u0631 \u062A\u062D\u0645\u064A\u0644 \u0627\u0644\u0627\u0633\u062A\u0628\u064A\u0627\u0646."
386
396
  };
387
397
  BN = {
388
398
  accept: "\u09AA\u09BE\u09A0\u09BE\u09A8",
@@ -396,7 +406,8 @@ var init_labels = __esm({
396
406
  closeAria: "\u09AA\u09AA\u0986\u09AA \u09AC\u09A8\u09CD\u09A7 \u0995\u09B0\u09C1\u09A8",
397
407
  loadingAria: "\u099C\u09B0\u09BF\u09AA \u09B2\u09CB\u09A1 \u09B9\u099A\u09CD\u099B\u09C7",
398
408
  errorRequired: "\u099A\u09BE\u09B2\u09BF\u09AF\u09BC\u09C7 \u09AF\u09C7\u09A4\u09C7 \u0986\u09AC\u09B6\u09CD\u09AF\u0995 \u09AA\u09CD\u09B0\u09B6\u09CD\u09A8\u09C7\u09B0 \u0989\u09A4\u09CD\u09A4\u09B0 \u09A6\u09BF\u09A8\u0964",
399
- errorSubmit: "\u099C\u09AE\u09BE \u09A6\u09C7\u0993\u09AF\u09BC\u09BE\u09B0 \u09B8\u09AE\u09AF\u09BC \u098F\u0995\u099F\u09BF \u09A4\u09CD\u09B0\u09C1\u099F\u09BF \u0998\u099F\u09C7\u099B\u09C7\u0964 \u0986\u09AC\u09BE\u09B0 \u099A\u09C7\u09B7\u09CD\u099F\u09BE \u0995\u09B0\u09C1\u09A8 \u09AC\u09BE \u09AA\u09AA\u0986\u09AA \u09AC\u09A8\u09CD\u09A7 \u0995\u09B0\u09C1\u09A8\u0964"
409
+ errorSubmit: "\u099C\u09AE\u09BE \u09A6\u09C7\u0993\u09AF\u09BC\u09BE\u09B0 \u09B8\u09AE\u09AF\u09BC \u098F\u0995\u099F\u09BF \u09A4\u09CD\u09B0\u09C1\u099F\u09BF \u0998\u099F\u09C7\u099B\u09C7\u0964 \u0986\u09AC\u09BE\u09B0 \u099A\u09C7\u09B7\u09CD\u099F\u09BE \u0995\u09B0\u09C1\u09A8 \u09AC\u09BE \u09AA\u09AA\u0986\u09AA \u09AC\u09A8\u09CD\u09A7 \u0995\u09B0\u09C1\u09A8\u0964",
410
+ errorLoad: "\u09B8\u09AE\u09C0\u0995\u09CD\u09B7\u09BE\u099F\u09BF \u09B2\u09CB\u09A1 \u0995\u09B0\u09BE \u09AF\u09BE\u09AF\u09BC\u09A8\u09BF\u0964"
400
411
  };
401
412
  ZH_CN = {
402
413
  accept: "\u53D1\u9001",
@@ -410,7 +421,8 @@ var init_labels = __esm({
410
421
  closeAria: "\u5173\u95ED\u5F39\u7A97",
411
422
  loadingAria: "\u6B63\u5728\u52A0\u8F7D\u95EE\u5377",
412
423
  errorRequired: "\u8BF7\u56DE\u7B54\u5FC5\u7B54\u9898\u540E\u7EE7\u7EED\u3002",
413
- errorSubmit: "\u63D0\u4EA4\u65F6\u51FA\u9519\u3002\u8BF7\u91CD\u8BD5\u6216\u5173\u95ED\u5F39\u7A97\u3002"
424
+ errorSubmit: "\u63D0\u4EA4\u65F6\u51FA\u9519\u3002\u8BF7\u91CD\u8BD5\u6216\u5173\u95ED\u5F39\u7A97\u3002",
425
+ errorLoad: "\u65E0\u6CD5\u52A0\u8F7D\u95EE\u5377\u3002"
414
426
  };
415
427
  LABELS = {
416
428
  en: EN,
@@ -5824,10 +5836,11 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5824
5836
  closeBtn.style.color = theme.textPrimary;
5825
5837
  closeBtn.style.transform = "scale(1)";
5826
5838
  };
5827
- closeBtn.onclick = () => {
5839
+ const closeFromChrome = () => {
5828
5840
  emit("popup_clicked", surveyId, { action: "close_icon" });
5829
5841
  onClose();
5830
5842
  };
5843
+ closeBtn.onclick = closeFromChrome;
5831
5844
  closeBtn.style.flex = "0 0 auto";
5832
5845
  closeBtn.style.marginLeft = "auto";
5833
5846
  header.appendChild(closeBtn);
@@ -5935,6 +5948,12 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
5935
5948
  formWrapper.appendChild(spinnerEl);
5936
5949
  formWrapper.appendChild(formHost);
5937
5950
  main.appendChild(formWrapper);
5951
+ const loadErrorEl = document.createElement("div");
5952
+ loadErrorEl.id = "dd-load-error";
5953
+ loadErrorEl.className = "deepdots-load-error";
5954
+ loadErrorEl.setAttribute("role", "alert");
5955
+ loadErrorEl.style.cssText = `display:none; width:100%; text-align:center; padding:24px 0; font-size:15px; color:${theme.textPrimary};`;
5956
+ main.appendChild(loadErrorEl);
5938
5957
  main.appendChild(errorHint);
5939
5958
  const footer = document.createElement("div");
5940
5959
  footer.id = "dd-footer";
@@ -6050,6 +6069,13 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6050
6069
  justify-content: center;
6051
6070
  text-align: center;
6052
6071
  `;
6072
+ const cancelButton = document.createElement("button");
6073
+ cancelButton.id = "dd-cancel";
6074
+ cancelButton.type = "button";
6075
+ cancelButton.className = "dd-nav-btn";
6076
+ cancelButton.textContent = actionLabels.decline;
6077
+ cancelButton.style.cssText = submitButton.style.cssText;
6078
+ cancelButton.onclick = closeFromChrome;
6053
6079
  submitButton.onclick = () => {
6054
6080
  if (!surveyCompletedEmitted) {
6055
6081
  emit("popup_clicked", surveyId, { action: "manual_send" });
@@ -6063,6 +6089,8 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6063
6089
  startButton.textContent = actionLabels.start;
6064
6090
  closeButton.textContent = actionLabels.complete;
6065
6091
  submitButton.textContent = actionLabels.accept;
6092
+ cancelButton.textContent = actionLabels.decline;
6093
+ loadErrorEl.textContent = labels.errorLoad;
6066
6094
  closeBtn.setAttribute("aria-label", labels.closeAria);
6067
6095
  spinnerEl.setAttribute("aria-label", labels.loadingAria);
6068
6096
  progressFollowUp.textContent = labels.followUp;
@@ -6073,10 +6101,12 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6073
6101
  startButton.style.display = "none";
6074
6102
  submitButton.style.display = "none";
6075
6103
  closeButton.style.display = "none";
6104
+ cancelButton.style.display = "none";
6076
6105
  footer.appendChild(submitButton);
6077
6106
  footer.appendChild(backButton);
6078
6107
  footer.appendChild(closeButton);
6079
6108
  footer.appendChild(startButton);
6109
+ footer.appendChild(cancelButton);
6080
6110
  containerContent.appendChild(main);
6081
6111
  footer.style.flex = "0 0 auto";
6082
6112
  containerContent.appendChild(footer);
@@ -6085,6 +6115,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6085
6115
  startButton.style.display = "none";
6086
6116
  submitButton.style.display = "none";
6087
6117
  closeButton.style.display = "none";
6118
+ cancelButton.style.display = "none";
6088
6119
  backButton.style.width = "";
6089
6120
  startButton.style.width = "";
6090
6121
  submitButton.style.width = "";
@@ -6114,6 +6145,9 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6114
6145
  case "error":
6115
6146
  setLoading(false);
6116
6147
  break;
6148
+ case "load_error":
6149
+ cancelButton.style.display = "inline-flex";
6150
+ break;
6117
6151
  }
6118
6152
  }
6119
6153
  function updateNavButtons() {
@@ -6135,6 +6169,20 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6135
6169
  img.style.cssText = "max-width:100%; height:auto; margin:0 auto 16px auto; display:block;";
6136
6170
  });
6137
6171
  }
6172
+ let loadFailed = false;
6173
+ function showLoadError(reason) {
6174
+ sdkWarn("[Deepdots] survey failed to load:", reason);
6175
+ if (loadFailed) return;
6176
+ loadFailed = true;
6177
+ progressEl.style.display = "none";
6178
+ formWrapper.style.display = "none";
6179
+ errorHint.style.display = "none";
6180
+ loadErrorEl.textContent = labels.errorLoad;
6181
+ loadErrorEl.style.display = "block";
6182
+ setLoading(false);
6183
+ updateButtons("load_error");
6184
+ reveal();
6185
+ }
6138
6186
  popup.appendChild(header);
6139
6187
  popup.appendChild(progressEl);
6140
6188
  popup.appendChild(containerContent);
@@ -6199,7 +6247,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6199
6247
  try {
6200
6248
  if (!import_native.default || typeof import_native.default.form !== "function") {
6201
6249
  sdkWarn("[MagicFeedback] form() no disponible. Fallback manual.");
6202
- setLoading(false);
6250
+ showLoadError("form() not available");
6203
6251
  return;
6204
6252
  }
6205
6253
  import_native.default.init(
@@ -6225,7 +6273,11 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6225
6273
  // quitado el `inert`. La apertura va aparte (ver autofocusOnOpen).
6226
6274
  autofocus: "navigation"
6227
6275
  };
6228
- generateOptions.onLoadedEvent = ({ formData, lang }) => {
6276
+ generateOptions.onLoadedEvent = ({ formData, lang, error }) => {
6277
+ if (error) {
6278
+ showLoadError(error);
6279
+ return;
6280
+ }
6229
6281
  const shownLang = typeof lang === "string" && lang.trim().length > 0 ? lang : void 0;
6230
6282
  const surveyLang = shownLang ?? (Array.isArray(formData?.lang) ? formData.lang.find((l) => typeof l === "string" && l.trim().length > 0) : void 0);
6231
6283
  if (surveyLang) applyLabels(surveyLang);
@@ -6324,11 +6376,11 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
6324
6376
  };
6325
6377
  formInstance.generate(formDivId, generateOptions).catch((err) => {
6326
6378
  sdkError("[MagicFeedback] Error generating form:", err);
6327
- setLoading(false);
6379
+ showLoadError(err);
6328
6380
  });
6329
6381
  } catch (e) {
6330
6382
  sdkError("[MagicFeedback] Exception initializing form:", e);
6331
- setLoading(false);
6383
+ showLoadError(e);
6332
6384
  }
6333
6385
  }
6334
6386
  var import_native, SURVEY_ALIGNMENT_STYLE_ID;
@@ -6664,12 +6716,14 @@ function buildSurveyHtml(opts) {
6664
6716
  const startLabel = jsonForScript(initActionLabels.start);
6665
6717
  const completeLabel = jsonForScript(initActionLabels.complete);
6666
6718
  const submitLabel = jsonForScript(initActionLabels.accept);
6719
+ const cancelLabel = jsonForScript(initActionLabels.decline);
6667
6720
  const labelsTableJson = jsonForScript(LABELS);
6668
6721
  const actionOverridesJson = jsonForScript({
6669
6722
  accept: trimmedOrNull(opts.actions?.accept?.label),
6670
6723
  start: trimmedOrNull(opts.actions?.start?.label),
6671
6724
  complete: trimmedOrNull(opts.actions?.complete?.label),
6672
- back: trimmedOrNull(opts.actions?.back?.label)
6725
+ back: trimmedOrNull(opts.actions?.back?.label),
6726
+ decline: trimmedOrNull(opts.actions?.decline?.label)
6673
6727
  });
6674
6728
  const initLangJson = jsonForScript(opts.language ?? "");
6675
6729
  const initDirection = directionFor(opts.language);
@@ -6731,6 +6785,7 @@ body.dd-ready #dd-popup{visibility:visible}
6731
6785
  #dd-logo{max-height:40px;max-width:100%;object-fit:contain;display:block;margin:12px 0 0 0}
6732
6786
  .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}
6733
6787
  /* Pantalla final: el HTML del editor de la plataforma (imagen + texto), centrado. */
6788
+ .deepdots-load-error{display:none;width:100%;text-align:center;padding:24px 0;font-size:15px;color:${textPrimary}}
6734
6789
  .deepdots-success{display:none;width:100%;text-align:center;padding:24px 0;color:${textPrimary}}
6735
6790
  .deepdots-success img{max-width:100%;height:auto;margin:0 auto 16px auto;display:block}
6736
6791
  .deepdots-success p{margin:0;font-size:16px;font-weight:600;line-height:1.4}
@@ -6738,7 +6793,7 @@ body.dd-ready #dd-popup{visibility:visible}
6738
6793
  .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}
6739
6794
  /* Secundario como bot\xF3n de texto: sin borde ni fondo, para que el primario sea la \xFAnica CTA. */
6740
6795
  #dd-back{background:transparent;color:${textMuted};border:none}
6741
- #dd-start,#dd-complete,#dd-submit{background:#1E293B;color:#fff;border:none}
6796
+ #dd-start,#dd-complete,#dd-submit,#dd-cancel{background:#1E293B;color:#fff;border:none}
6742
6797
  #dd-submit:disabled,#dd-start:disabled,#dd-back:disabled,#dd-complete:disabled{opacity:0.6;cursor:not-allowed}
6743
6798
  .mf-spinner{display:none;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}
6744
6799
  .mf-spinner-circle{width:28px;height:28px;border:3px solid #e0e6ed;border-top-color:#1E293B;border-radius:50%;animation:ddspin 0.9s linear infinite}
@@ -6779,6 +6834,7 @@ ${customCss}
6779
6834
  <div class="mf-spinner" id="dd-spinner" role="status" aria-label="${initLabels.loadingAria}"><div class="mf-spinner-circle"></div></div>
6780
6835
  <div id="mf"></div>
6781
6836
  </div>
6837
+ <div class="deepdots-load-error" id="dd-load-error" role="alert"></div>
6782
6838
  <div class="deepdots-error-hint" id="dd-error" role="alert" aria-live="polite"></div>
6783
6839
  </div>
6784
6840
  <div class="deepdots-popup-footer" id="dd-footer">
@@ -6786,6 +6842,7 @@ ${customCss}
6786
6842
  <button type="button" class="dd-nav-btn" id="dd-back"></button>
6787
6843
  <button type="button" class="dd-nav-btn" id="dd-complete"></button>
6788
6844
  <button type="button" class="dd-nav-btn" id="dd-start"></button>
6845
+ <button type="button" class="dd-nav-btn" id="dd-cancel"></button>
6789
6846
  </div>
6790
6847
  </div>
6791
6848
  </div>
@@ -6808,6 +6865,8 @@ ${customCss}
6808
6865
  var startBtn=document.getElementById('dd-start');
6809
6866
  var completeBtn=document.getElementById('dd-complete');
6810
6867
  var submitBtn=document.getElementById('dd-submit');
6868
+ var cancelBtn=document.getElementById('dd-cancel');
6869
+ var loadErrorEl=document.getElementById('dd-load-error');
6811
6870
  var titleEl=document.getElementById('dd-title');
6812
6871
  var progressEl=document.getElementById('dd-progress');
6813
6872
  var progressLabel=document.getElementById('dd-progress-label');
@@ -6837,6 +6896,8 @@ ${customCss}
6837
6896
  startBtn.textContent=DD_ACTION_OVERRIDES.start||next.start;
6838
6897
  completeBtn.textContent=DD_ACTION_OVERRIDES.complete||next.complete;
6839
6898
  submitBtn.textContent=DD_ACTION_OVERRIDES.accept||next.accept;
6899
+ cancelBtn.textContent=DD_ACTION_OVERRIDES.decline||next.decline;
6900
+ loadErrorEl.textContent=next.errorLoad;
6840
6901
  if(closeEl){ closeEl.setAttribute('aria-label',next.closeAria); }
6841
6902
  if(spinner){ spinner.setAttribute('aria-label',next.loadingAria); }
6842
6903
  progressFollowUp.textContent=next.followUp;
@@ -6891,6 +6952,7 @@ ${customCss}
6891
6952
  startBtn.textContent=${startLabel};
6892
6953
  completeBtn.textContent=${completeLabel};
6893
6954
  submitBtn.textContent=${submitLabel};
6955
+ cancelBtn.textContent=${cancelLabel};
6894
6956
 
6895
6957
  closeEl.onclick=function(){ emitJSON('popup_close'); };
6896
6958
  backBtn.onclick=function(){ if(window.DeepdotsForm && window.DeepdotsForm.back){ window.DeepdotsForm.back(); } };
@@ -6902,6 +6964,8 @@ ${customCss}
6902
6964
  }
6903
6965
  };
6904
6966
  completeBtn.onclick=function(){ emitJSON('popup_close'); };
6967
+ // \xDAnica acci\xF3n del survey que no carga: cierra igual que la X.
6968
+ cancelBtn.onclick=function(){ emitJSON('popup_close'); };
6905
6969
  var surveyCompletedEmitted=false;
6906
6970
  submitBtn.onclick=function(){
6907
6971
  if(surveyCompletedEmitted) return;
@@ -6943,6 +7007,7 @@ ${customCss}
6943
7007
  startBtn.style.display='none';
6944
7008
  submitBtn.style.display='none';
6945
7009
  completeBtn.style.display='none';
7010
+ cancelBtn.style.display='none';
6946
7011
  backBtn.style.width='';
6947
7012
  startBtn.style.width='';
6948
7013
  submitBtn.style.width='';
@@ -6958,6 +7023,8 @@ ${customCss}
6958
7023
  } else if(state==='completed'){
6959
7024
  completeBtn.style.display='inline-flex';
6960
7025
  completeBtn.style.width='100%';
7026
+ } else if(state==='load_error'){
7027
+ cancelBtn.style.display='inline-flex';
6961
7028
  }
6962
7029
  }
6963
7030
 
@@ -7004,6 +7071,26 @@ ${REVEAL_JS}
7004
7071
  }
7005
7072
  setLoading(true);
7006
7073
 
7074
+ // Survey que no ha cargado (406 de /info, sin red, bundle del CDN ca\xEDdo). Antes se emit\xEDa
7075
+ // \`loaded\` y se pintaba un Send sobre un survey vac\xEDo. Espejo de KMP: aviso traducido, la X
7076
+ // sigue, un \xFAnico Cancel, y se revela ya (manda \`ready\`): no hay nada m\xE1s que esperar.
7077
+ // No es interacci\xF3n del usuario: el renderer no lo convierte en popup_clicked.
7078
+ var ddLoadFailed=false;
7079
+ function ddShowLoadError(name, reason){
7080
+ if(ddLoadFailed){ return; }
7081
+ ddLoadFailed=true;
7082
+ var why=(reason && reason.message) ? reason.message : String(reason);
7083
+ emitJSON(name, { message: why });
7084
+ progressEl.style.display='none';
7085
+ formWrapper.style.display='none';
7086
+ errorHint.style.display='none';
7087
+ loadErrorEl.textContent=DD_LABELS_ACTIVE.errorLoad;
7088
+ loadErrorEl.style.display='block';
7089
+ setLoading(false);
7090
+ updateButtons('load_error');
7091
+ ddReveal.reveal();
7092
+ }
7093
+
7007
7094
  var initialized=false;
7008
7095
  function initMF(){
7009
7096
  if(initialized || !window.magicfeedback) return initialized;
@@ -7027,6 +7114,7 @@ ${REVEAL_JS}
7027
7114
  // de native sin esta opci\xF3n la ignoran.
7028
7115
  autofocus:'navigation',
7029
7116
  onLoadedEvent:function(args){
7117
+ if(args && args.error){ ddShowLoadError('error:load', args.error); return; }
7030
7118
  // Idioma en el que native muestra el survey: manda sobre el del init para que el
7031
7119
  // chrome no se quede en ingl\xE9s delante de un survey traducido. Native < 2.3 no lo
7032
7120
  // env\xEDa: entonces el primero de formData.lang (el idioma por defecto).
@@ -7130,13 +7218,13 @@ ${REVEAL_JS}
7130
7218
  updateProgress({ progress:p&&p.progress, total:form.total, completed:false, followup:p&&p.followup });
7131
7219
  emitJSON('back', p);
7132
7220
  }
7133
- }).catch(function(e){ setLoading(false); emit('error:init'); });
7134
- }catch(e){ setLoading(false); emit('error:exception'); }
7221
+ }).catch(function(e){ ddShowLoadError('error:init', e); });
7222
+ }catch(e){ ddShowLoadError('error:exception', e); }
7135
7223
  return true;
7136
7224
  }
7137
7225
  var s=document.createElement('script'); s.src=${JSON.stringify(cdn)}; s.async=true;
7138
7226
  s.onload=function(){ initMF(); };
7139
- s.onerror=function(){ setLoading(false); emit('error:load'); };
7227
+ s.onerror=function(){ ddShowLoadError('error:load', 'survey script failed to load'); };
7140
7228
  document.head.appendChild(s);
7141
7229
  var tries=0, t=setInterval(function(){ if(initMF()||++tries>40){ clearInterval(t); } },150);
7142
7230
  })();
@@ -7255,6 +7343,7 @@ var ReactNativePopupRenderer = class {
7255
7343
  this.onCloseFn?.();
7256
7344
  break;
7257
7345
  default:
7346
+ if (typeof name === "string" && name.startsWith("error:")) this.emitReady(surveyId);
7258
7347
  break;
7259
7348
  }
7260
7349
  }
@@ -8065,12 +8154,12 @@ var EngagementTracker = class {
8065
8154
 
8066
8155
  // src/contact/contact-manager.ts
8067
8156
  init_tracking_manager();
8068
- function stableSerialize(attrs) {
8157
+ function stableSerialize(userId, attrs) {
8069
8158
  const sorted = {};
8070
8159
  for (const key of Object.keys(attrs).sort()) {
8071
8160
  sorted[key] = attrs[key];
8072
8161
  }
8073
- return JSON.stringify(sorted);
8162
+ return JSON.stringify({ userId, attributes: sorted });
8074
8163
  }
8075
8164
  var ContactManager = class {
8076
8165
  constructor(options) {
@@ -8080,18 +8169,23 @@ var ContactManager = class {
8080
8169
  this.post = options.post;
8081
8170
  }
8082
8171
  /**
8083
- * Envía los atributos si cambiaron respecto a lo último enviado.
8084
- * @returns `true` si se envió, `false` si no hubo cambios.
8172
+ * Envía los atributos si cambiaron respecto a lo último enviado. La caché solo se guarda
8173
+ * cuando la API acepta el envío; si falla, la llamada siguiente reintenta. Nunca rechaza.
8174
+ * @returns `true` si se envió, `false` si no hubo cambios o el envío falló.
8085
8175
  */
8086
8176
  async setAttributes(attributes) {
8087
- const serialized = stableSerialize(attributes);
8177
+ const serialized = stableSerialize(this.userId, attributes);
8088
8178
  const last = this.storage.getItem(STORAGE_KEYS.contactAttributes);
8089
8179
  if (last === serialized) return false;
8090
- await this.post({
8091
- publicKey: this.publicKey,
8092
- userId: this.userId,
8093
- userAttributes: attributes
8094
- });
8180
+ try {
8181
+ await this.post({
8182
+ publicKey: this.publicKey,
8183
+ userId: this.userId,
8184
+ userAttributes: attributes
8185
+ });
8186
+ } catch {
8187
+ return false;
8188
+ }
8095
8189
  this.storage.setItem(STORAGE_KEYS.contactAttributes, serialized);
8096
8190
  return true;
8097
8191
  }
@@ -9488,25 +9582,31 @@ var DeepdotsPopups = class {
9488
9582
  return null;
9489
9583
  }
9490
9584
  }
9491
- /** Envía los atributos de contact a la API (`POST /sdk/popups/contact`). */
9585
+ /**
9586
+ * Envía los atributos de contact a la API (`POST /sdk/popups/contact`). Rechaza si no se
9587
+ * pudo enviar (sin red o respuesta no-2xx), para que `ContactManager` no guarde la caché
9588
+ * y la llamada siguiente reintente.
9589
+ */
9492
9590
  async postContact(body) {
9493
9591
  const baseUrl = this.baseUrl;
9494
- if (!baseUrl) return;
9592
+ if (!baseUrl) throw new Error("no base URL");
9495
9593
  const endpoint = `${baseUrl}/sdk/popups/contact`;
9594
+ let response;
9496
9595
  try {
9497
- const response = await fetch(endpoint, {
9596
+ response = await fetch(endpoint, {
9498
9597
  method: "POST",
9499
9598
  headers: { "Content-Type": "application/json" },
9500
9599
  body: JSON.stringify(body)
9501
9600
  });
9502
- if (!response.ok) {
9503
- this.log("Failed to post contact", response.status, response.statusText);
9504
- return;
9505
- }
9506
- this.log("contact \xB7 sent attributes for user:", body.userId);
9507
9601
  } catch (error) {
9508
9602
  this.log("Error posting contact", error);
9603
+ throw error;
9604
+ }
9605
+ if (!response.ok) {
9606
+ this.log("Failed to post contact", response.status, response.statusText);
9607
+ throw new Error(`HTTP ${response.status}`);
9509
9608
  }
9609
+ this.log("contact \xB7 sent attributes for user:", body.userId);
9510
9610
  }
9511
9611
  /** Notifica eventos de popup a la API */
9512
9612
  async postPopupEvent(status, popupId, userId) {