@magicfeedback/popup-sdk 1.7.2 → 1.8.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.
@@ -538,7 +538,27 @@ declare class DeepdotsPopups {
538
538
  private showDefinition;
539
539
  private shouldShow;
540
540
  private matchesSegmentsLang;
541
+ /** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */
541
542
  private matchesSegmentsPath;
543
+ /**
544
+ * `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre
545
+ * `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el
546
+ * carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).
547
+ */
548
+ private isPathExcluded;
549
+ /**
550
+ * Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta
551
+ * actual. Mismas reglas para incluir y para excluir:
552
+ * - URL absoluta → href completo exacto
553
+ * - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)
554
+ * - resto → pathname exacto
555
+ * Ignora entradas que no sean string: el backend puede colar un `null` en la lista
556
+ * y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con
557
+ * `segments.lang`).
558
+ */
559
+ private matchesPathCandidate;
560
+ /** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */
561
+ private resolveCurrentLocation;
542
562
  private evaluateCooldown;
543
563
  private evaluateLegacyCondition;
544
564
  private hasCooldownElapsed;
@@ -538,7 +538,27 @@ declare class DeepdotsPopups {
538
538
  private showDefinition;
539
539
  private shouldShow;
540
540
  private matchesSegmentsLang;
541
+ /** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */
541
542
  private matchesSegmentsPath;
543
+ /**
544
+ * `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre
545
+ * `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el
546
+ * carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).
547
+ */
548
+ private isPathExcluded;
549
+ /**
550
+ * Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta
551
+ * actual. Mismas reglas para incluir y para excluir:
552
+ * - URL absoluta → href completo exacto
553
+ * - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)
554
+ * - resto → pathname exacto
555
+ * Ignora entradas que no sean string: el backend puede colar un `null` en la lista
556
+ * y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con
557
+ * `segments.lang`).
558
+ */
559
+ private matchesPathCandidate;
560
+ /** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */
561
+ private resolveCurrentLocation;
542
562
  private evaluateCooldown;
543
563
  private evaluateLegacyCondition;
544
564
  private hasCooldownElapsed;
@@ -522,6 +522,29 @@ function ddApplySurveyPrimaryColor(el, style){
522
522
  }
523
523
  });
524
524
 
525
+ // src/ui/busy.ts
526
+ function setSurveyBusy(el, busy) {
527
+ if (!el) return;
528
+ el.style.pointerEvents = busy ? "none" : "";
529
+ el.setAttribute("aria-busy", busy ? "true" : "false");
530
+ if ("inert" in el) {
531
+ el.inert = busy;
532
+ }
533
+ }
534
+ var BUSY_JS;
535
+ var init_busy = __esm({
536
+ "src/ui/busy.ts"() {
537
+ "use strict";
538
+ BUSY_JS = `
539
+ function ddSetSurveyBusy(el, busy){
540
+ if(!el){ return; }
541
+ el.style.pointerEvents = busy ? 'none' : '';
542
+ el.setAttribute('aria-busy', busy ? 'true' : 'false');
543
+ if('inert' in el){ el.inert = busy; }
544
+ }`;
545
+ }
546
+ });
547
+
525
548
  // src/assets/style.css
526
549
  var style_default;
527
550
  var init_style = __esm({
@@ -2964,6 +2987,7 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
2964
2987
  revealTimer = setTimeout(reveal, REVEAL_TIMEOUT_MS);
2965
2988
  function setLoading(isLoading) {
2966
2989
  spinnerEl.style.display = isLoading ? "flex" : "none";
2990
+ setSurveyBusy(formWrapper, isLoading);
2967
2991
  if (!isLoading) {
2968
2992
  formHost.style.visibility = "visible";
2969
2993
  revealWhenPainted2();
@@ -3119,6 +3143,7 @@ var init_renderPopup = __esm({
3119
3143
  init_font();
3120
3144
  init_logo();
3121
3145
  init_surveyPalette();
3146
+ init_busy();
3122
3147
  init_logger();
3123
3148
  init_labels();
3124
3149
  init_reveal();
@@ -3338,6 +3363,7 @@ init_font();
3338
3363
  init_labels();
3339
3364
  init_reveal();
3340
3365
  init_surveyPalette();
3366
+ init_busy();
3341
3367
  init_style();
3342
3368
  var REVEAL_JS = `
3343
3369
  function ddCreateReveal(popup, onReveal, timeoutMs){
@@ -3557,6 +3583,7 @@ ${customCss}
3557
3583
  var footer=document.getElementById('dd-footer');
3558
3584
  var spinner=document.getElementById('dd-spinner');
3559
3585
  var formHost=document.getElementById('mf');
3586
+ var formWrapper=document.getElementById('dd-form-wrapper');
3560
3587
  var errorHint=document.getElementById('dd-error');
3561
3588
  var backBtn=document.getElementById('dd-back');
3562
3589
  var startBtn=document.getElementById('dd-start');
@@ -3716,6 +3743,7 @@ ${customCss}
3716
3743
  }
3717
3744
 
3718
3745
  ${PRIMARY_COLOR_JS}
3746
+ ${BUSY_JS}
3719
3747
  ${REVEAL_JS}
3720
3748
  // El popup se ense\xF1a cuando el survey est\xE1 pintado; \`ready\` deja al host abrir su propio
3721
3749
  // Modal en ese momento en vez de montar un WebView en blanco.
@@ -3726,6 +3754,11 @@ ${REVEAL_JS}
3726
3754
 
3727
3755
  function setLoading(isLoading){
3728
3756
  spinner.style.display=isLoading?'flex':'none';
3757
+ // El spinner no tapa el survey (28px en posicion absoluta), asi que sin esto el usuario
3758
+ // puede seguir cambiando de opcion mientras se envia la pagina, y ese cambio ya no viaja
3759
+ // con ella. Se bloquea el wrapper, que es el elemento estable: el div de dentro lo
3760
+ // sustituye @magicfeedback/native por su propio contenedor al generar el survey.
3761
+ ddSetSurveyBusy(formWrapper, isLoading);
3729
3762
  if(!isLoading){
3730
3763
  formHost.style.visibility='visible';
3731
3764
  // Fin de la carga, sea por survey cargado o por error. Entre p\xE1ginas es no-op: la
@@ -5508,6 +5541,9 @@ var DeepdotsPopups = class {
5508
5541
  this.emitEvent("popup_shown", def.surveyId, { popupId: def.id });
5509
5542
  }
5510
5543
  shouldShow(def, pathUrl, skipPathCheck = false) {
5544
+ if (this.isPathExcluded(def, pathUrl)) {
5545
+ return false;
5546
+ }
5511
5547
  if (!skipPathCheck && !this.matchesSegmentsPath(def, pathUrl)) {
5512
5548
  return false;
5513
5549
  }
@@ -5538,40 +5574,85 @@ var DeepdotsPopups = class {
5538
5574
  }
5539
5575
  return matches;
5540
5576
  }
5577
+ /** `segments.path`: rutas donde el popup PUEDE mostrarse. Sin lista, en todas. */
5541
5578
  matchesSegmentsPath(def, pathUrl) {
5542
5579
  const paths = def.segments?.path;
5543
- if (!paths || paths.length === 0) return true;
5544
- if (typeof window === "undefined" || !window.location) {
5580
+ if (!Array.isArray(paths) || paths.length === 0) return true;
5581
+ const location = this.resolveCurrentLocation(pathUrl);
5582
+ if (!location) {
5545
5583
  this.debug("No window.location available for path comparison", { popupId: def.id, paths });
5546
5584
  return true;
5547
5585
  }
5548
- const normalizedHref = this.normalizeUrl(pathUrl || window.location.href || "");
5549
- const currentUrl = this.safeParseUrl(normalizedHref);
5550
- const normalizedPath = this.normalizeUrl(currentUrl?.pathname || window.location.pathname || "");
5551
5586
  const matches = paths.some((rawCandidate) => {
5552
- const candidate = this.normalizeUrl(rawCandidate);
5553
- let match = false;
5554
- if (candidate.startsWith("http://") || candidate.startsWith("https://")) {
5555
- match = normalizedHref === candidate;
5556
- } else if (candidate.startsWith("/")) {
5557
- match = normalizedHref.includes(candidate);
5558
- } else {
5559
- match = normalizedPath === candidate;
5560
- }
5587
+ const match = this.matchesPathCandidate(rawCandidate, location);
5561
5588
  this.debug("Path comparison", {
5562
5589
  popupId: def.id,
5563
- candidate,
5564
- currentPath: normalizedPath,
5565
- currentHref: normalizedHref,
5590
+ candidate: rawCandidate,
5591
+ currentPath: location.path,
5592
+ currentHref: location.href,
5566
5593
  match
5567
5594
  });
5568
5595
  return match;
5569
5596
  });
5570
5597
  if (!matches) {
5571
- this.debug("No path match for popup", { popupId: def.id, paths, currentPath: normalizedPath });
5598
+ this.debug("No path match for popup", { popupId: def.id, paths, currentPath: location.path });
5572
5599
  }
5573
5600
  return matches;
5574
5601
  }
5602
+ /**
5603
+ * `segments.excludedPaths`: rutas donde el popup NO debe mostrarse. Gana sobre
5604
+ * `segments.path` (`path: ['/']` + `excludedPaths: ['/cart']` = en todas menos el
5605
+ * carrito) y se aplica también cuando no hay `path` (= en todas menos las excluidas).
5606
+ */
5607
+ isPathExcluded(def, pathUrl) {
5608
+ const excluded = def.segments?.excludedPaths;
5609
+ if (!Array.isArray(excluded) || excluded.length === 0) return false;
5610
+ const location = this.resolveCurrentLocation(pathUrl);
5611
+ if (!location) {
5612
+ this.debug("No window.location available for excluded path comparison", { popupId: def.id, excluded });
5613
+ return false;
5614
+ }
5615
+ const hit = excluded.find((rawCandidate) => this.matchesPathCandidate(rawCandidate, location));
5616
+ if (hit !== void 0) {
5617
+ this.debug("Popup excluded by path", {
5618
+ popupId: def.id,
5619
+ candidate: hit,
5620
+ currentPath: location.path,
5621
+ currentHref: location.href
5622
+ });
5623
+ return true;
5624
+ }
5625
+ return false;
5626
+ }
5627
+ /**
5628
+ * Compara UN candidato de `segments.path`/`segments.excludedPaths` con la ruta
5629
+ * actual. Mismas reglas para incluir y para excluir:
5630
+ * - URL absoluta → href completo exacto
5631
+ * - empieza por / → subcadena del href (así entran las rutas con hash: `/#/home`)
5632
+ * - resto → pathname exacto
5633
+ * Ignora entradas que no sean string: el backend puede colar un `null` en la lista
5634
+ * y no puede tumbar la evaluación del trigger entero (mismo fallo que hubo con
5635
+ * `segments.lang`).
5636
+ */
5637
+ matchesPathCandidate(rawCandidate, location) {
5638
+ if (typeof rawCandidate !== "string") return false;
5639
+ const candidate = this.normalizeUrl(rawCandidate);
5640
+ if (!candidate) return false;
5641
+ if (candidate.startsWith("http://") || candidate.startsWith("https://")) {
5642
+ return location.href === candidate;
5643
+ }
5644
+ if (candidate.startsWith("/")) {
5645
+ return location.href.includes(candidate);
5646
+ }
5647
+ return location.path === candidate;
5648
+ }
5649
+ /** href + pathname normalizados de la ruta a evaluar, o null si no hay `window.location`. */
5650
+ resolveCurrentLocation(pathUrl) {
5651
+ if (typeof window === "undefined" || !window.location) return null;
5652
+ const href = this.normalizeUrl(pathUrl || window.location.href || "");
5653
+ const parsed = this.safeParseUrl(href);
5654
+ return { href, path: this.normalizeUrl(parsed?.pathname || window.location.pathname || "") };
5655
+ }
5575
5656
  evaluateCooldown(def, condition) {
5576
5657
  switch (condition.answered) {
5577
5658
  case "SHOWED":