@magicfeedback/popup-sdk 1.1.4 → 1.1.5

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.
@@ -195,12 +195,21 @@ interface PopupActions {
195
195
  start?: PopupActionStart;
196
196
  back?: PopupActionDecline;
197
197
  }
198
+ /** Fuente personalizable del popup + survey (viene de la API por popup) */
199
+ interface PopupFont {
200
+ /** Nombre de la familia. Ej: "Inter" (nombre limpio, NO un stack). */
201
+ family: string;
202
+ /** Opcional: URL a un archivo de fuente (woff2/ttf/otf) para armar un @font-face. */
203
+ url?: string;
204
+ }
198
205
  /** Configurable popup styles */
199
206
  interface PopupStyle {
200
207
  theme: 'light' | 'dark';
201
208
  position: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center';
202
209
  /** @deprecated Not used by the SDK; kept only for API compatibility */
203
210
  imageUrl?: string | null;
211
+ /** Fuente personalizada. Si falta, se mantiene el comportamiento actual. */
212
+ font?: PopupFont;
204
213
  }
205
214
 
206
215
  interface PopupRenderer {
@@ -509,7 +518,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
509
518
  private partialEmitted;
510
519
  constructor(options?: ReactNativeRendererOptions);
511
520
  init(): void;
512
- show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
521
+ show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
513
522
  hide(): void;
514
523
  /**
515
524
  * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
@@ -195,12 +195,21 @@ interface PopupActions {
195
195
  start?: PopupActionStart;
196
196
  back?: PopupActionDecline;
197
197
  }
198
+ /** Fuente personalizable del popup + survey (viene de la API por popup) */
199
+ interface PopupFont {
200
+ /** Nombre de la familia. Ej: "Inter" (nombre limpio, NO un stack). */
201
+ family: string;
202
+ /** Opcional: URL a un archivo de fuente (woff2/ttf/otf) para armar un @font-face. */
203
+ url?: string;
204
+ }
198
205
  /** Configurable popup styles */
199
206
  interface PopupStyle {
200
207
  theme: 'light' | 'dark';
201
208
  position: 'bottom' | 'bottom-right' | 'bottom-left' | 'top' | 'top-right' | 'top-left' | 'center';
202
209
  /** @deprecated Not used by the SDK; kept only for API compatibility */
203
210
  imageUrl?: string | null;
211
+ /** Fuente personalizada. Si falta, se mantiene el comportamiento actual. */
212
+ font?: PopupFont;
204
213
  }
205
214
 
206
215
  interface PopupRenderer {
@@ -509,7 +518,7 @@ declare class ReactNativePopupRenderer implements PopupRenderer {
509
518
  private partialEmitted;
510
519
  constructor(options?: ReactNativeRendererOptions);
511
520
  init(): void;
512
- show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, _style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
521
+ show(surveyId: string, productId: string, _actions: PopupActions | undefined, emit: EmitFn, onClose: () => void, env?: string, userId?: string, style?: PopupStyle, sessionId?: string, miniService?: string, analyticsFeedbackSessionId?: string): void;
513
522
  hide(): void;
514
523
  /**
515
524
  * El host la conecta al `onMessage` del WebView. Traduce los mensajes del survey a
@@ -141,6 +141,43 @@ var init_tracking_manager = __esm({
141
141
  }
142
142
  });
143
143
 
144
+ // src/ui/font.ts
145
+ function fontFormatFromUrl(url) {
146
+ const clean = url.split("?")[0].split("#")[0];
147
+ const ext = clean.slice(clean.lastIndexOf(".") + 1).toLowerCase();
148
+ return FORMAT_BY_EXT[ext];
149
+ }
150
+ function sanitizeFamily(family) {
151
+ return family.replace(/[^A-Za-z0-9 ._-]/g, "").trim();
152
+ }
153
+ function isSafeFontUrl(url) {
154
+ if (!/^(https?:|data:)/i.test(url)) return false;
155
+ return !/[\x00-\x20"<>\\]/.test(url);
156
+ }
157
+ function buildFontFamilyValue(family) {
158
+ const safe = sanitizeFamily(family);
159
+ return `"${safe}", -apple-system, system-ui, sans-serif`;
160
+ }
161
+ function buildFontFaceCss(family, url) {
162
+ if (!url || !isSafeFontUrl(url)) return "";
163
+ const safe = sanitizeFamily(family);
164
+ const fmt = fontFormatFromUrl(url);
165
+ const src = fmt ? `url("${url}") format("${fmt}")` : `url("${url}")`;
166
+ return `@font-face{font-family:"${safe}";src:${src};font-display:swap;}`;
167
+ }
168
+ var FORMAT_BY_EXT;
169
+ var init_font = __esm({
170
+ "src/ui/font.ts"() {
171
+ "use strict";
172
+ FORMAT_BY_EXT = {
173
+ woff2: "woff2",
174
+ woff: "woff",
175
+ ttf: "truetype",
176
+ otf: "opentype"
177
+ };
178
+ }
179
+ });
180
+
144
181
  // src/assets/style.css
145
182
  var style_default;
146
183
  var init_style = __esm({
@@ -285,13 +322,22 @@ var init_style = __esm({
285
322
  padding: 0;
286
323
  font-weight: 900;
287
324
  color: #222;
288
- font-family: 'Montserrat';
325
+ font-family: var(--deepdots-font, 'Montserrat');
289
326
  font-size: 18px;
290
327
  text-transform: uppercase;
291
328
  text-align: center;
292
329
  margin-bottom: 40px;
293
330
  }
294
331
 
332
+ /* Los controles de formulario no heredan font-family por defecto: cuando hay
333
+ fuente custom (clase deepdots-has-font), los forzamos a usar la del contenedor. */
334
+ .deepdots-popup.deepdots-has-font button,
335
+ .deepdots-popup.deepdots-has-font input,
336
+ .deepdots-popup.deepdots-has-font textarea,
337
+ .deepdots-popup.deepdots-has-font select {
338
+ font-family: var(--deepdots-font, inherit);
339
+ }
340
+
295
341
  .deepdots-popup h6 {
296
342
  margin-bottom: 1rem;
297
343
  }
@@ -1064,6 +1110,18 @@ function ensureResponsiveStyles(_popup) {
1064
1110
  `;
1065
1111
  document.head.appendChild(style);
1066
1112
  }
1113
+ function ensureFontFace(family, url) {
1114
+ const css = buildFontFaceCss(family, url);
1115
+ if (!css) return;
1116
+ const STYLE_ID = "deepdots-font-face";
1117
+ let el = document.getElementById(STYLE_ID);
1118
+ if (!el) {
1119
+ el = document.createElement("style");
1120
+ el.id = STYLE_ID;
1121
+ document.head.appendChild(el);
1122
+ }
1123
+ if (el.textContent !== css) el.textContent = css;
1124
+ }
1067
1125
  async function renderPopup(container, surveyId, productId, actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
1068
1126
  let surveyCompletedEmitted = false;
1069
1127
  let stylesInjected = false;
@@ -1099,6 +1157,14 @@ async function renderPopup(container, surveyId, productId, actions, emit, onClos
1099
1157
  width: 90%;
1100
1158
  min-height: 200px;
1101
1159
  `;
1160
+ const font = style?.font;
1161
+ if (font?.family) {
1162
+ const familyValue = buildFontFamilyValue(font.family);
1163
+ popup.style.fontFamily = familyValue;
1164
+ popup.style.setProperty("--deepdots-font", familyValue);
1165
+ popup.classList.add("deepdots-has-font");
1166
+ if (font.url) ensureFontFace(font.family, font.url);
1167
+ }
1102
1168
  const header = document.createElement("div");
1103
1169
  header.className = "deepdots-popup-header";
1104
1170
  header.style.cssText = "display:flex; justify-content:flex-end; align-items:center; width:100%;";
@@ -1520,6 +1586,7 @@ var init_renderPopup = __esm({
1520
1586
  "src/ui/renderPopup.ts"() {
1521
1587
  "use strict";
1522
1588
  init_tracking_manager();
1589
+ init_font();
1523
1590
  import_native = __toESM(require("@magicfeedback/native"));
1524
1591
  init_style();
1525
1592
  }
@@ -1726,6 +1793,7 @@ function resolveEnvironment(nodeEnv) {
1726
1793
  init_tracking_manager();
1727
1794
 
1728
1795
  // src/ui/surveyHtml.ts
1796
+ init_font();
1729
1797
  function buildSurveyHtml(opts) {
1730
1798
  const env = opts.env === "development" ? "dev" : "prod";
1731
1799
  const version = opts.version ?? "2.2.4";
@@ -1734,10 +1802,12 @@ function buildSurveyHtml(opts) {
1734
1802
  const pid = JSON.stringify(opts.productId);
1735
1803
  const profileJson = JSON.stringify(opts.profile ?? []);
1736
1804
  const metaJson = JSON.stringify(opts.metadata ?? []);
1805
+ const fontFaceCss = opts.font ? buildFontFaceCss(opts.font.family, opts.font.url) : "";
1806
+ const fontFamilyValue = opts.font ? buildFontFamilyValue(opts.font.family) : "-apple-system,system-ui,sans-serif";
1737
1807
  return `<!DOCTYPE html><html><head>
1738
1808
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
1739
1809
  <meta name="color-scheme" content="light"/>
1740
- <style>html,body{margin:0;padding:0;background:transparent;font-family:-apple-system,system-ui,sans-serif}#mf{width:100%;box-sizing:border-box}#mf *{max-width:100%;box-sizing:border-box}</style>
1810
+ <style>${fontFaceCss}html,body{margin:0;padding:0;background:transparent;font-family:${fontFamilyValue}}#mf{width:100%;box-sizing:border-box}#mf *{max-width:100%;box-sizing:border-box}</style>
1741
1811
  </head><body>
1742
1812
  <div id="mf"></div>
1743
1813
  <script>
@@ -1788,13 +1858,13 @@ var ReactNativePopupRenderer = class {
1788
1858
  }
1789
1859
  init() {
1790
1860
  }
1791
- show(surveyId, productId, _actions, emit, onClose, env = "production", userId, _style, sessionId, miniService, analyticsFeedbackSessionId) {
1861
+ show(surveyId, productId, _actions, emit, onClose, env = "production", userId, style, sessionId, miniService, analyticsFeedbackSessionId) {
1792
1862
  this.emitFn = emit;
1793
1863
  this.onCloseFn = onClose;
1794
1864
  this.currentSurveyId = surveyId;
1795
1865
  this.partialEmitted = false;
1796
1866
  const { profile, metadata } = buildSurveyIdentity(userId ?? null, sessionId ?? null, miniService ?? null, analyticsFeedbackSessionId ?? null);
1797
- const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata });
1867
+ const html = buildSurveyHtml({ surveyId, productId, env, profile, metadata, font: style?.font });
1798
1868
  if (this.options.onShow) {
1799
1869
  this.options.onShow({ surveyId, productId, html });
1800
1870
  } else {