@sitepulse/web 1.0.0 → 1.0.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.d.ts CHANGED
@@ -4,8 +4,10 @@ export * from "./renderer";
4
4
  export * from "./evaluator";
5
5
  export interface SitePulseConfig {
6
6
  siteId: string;
7
+ apiHost?: string;
7
8
  endpointUrl?: string;
8
9
  campaignsEndpointUrl?: string;
10
+ submissionsEndpointUrl?: string;
9
11
  flushIntervalMs?: number;
10
12
  autoTrack?: boolean;
11
13
  autoFetchCampaigns?: boolean;
package/dist/index.js CHANGED
@@ -1052,7 +1052,8 @@ function renderCampaignDOM(campaign, handlers = {}) {
1052
1052
  const emailVal = entries.email || entries.email_address || entries.newsletter_email || null;
1053
1053
  handlers.onSubmit?.(entries);
1054
1054
  if (typeof fetch !== "undefined") {
1055
- fetch("/api/campaigns/submit", {
1055
+ const submitEndpoint = handlers.submissionsEndpointUrl || "/api/campaigns/submit";
1056
+ fetch(submitEndpoint, {
1056
1057
  method: "POST",
1057
1058
  headers: { "Content-Type": "application/json" },
1058
1059
  body: JSON.stringify({
@@ -1240,6 +1241,7 @@ function renderCampaignDOM(campaign, handlers = {}) {
1240
1241
  let originalPaddingTop = null;
1241
1242
  let originalPaddingBottom = null;
1242
1243
  let resizeObserver = null;
1244
+ const shiftedElements = [];
1243
1245
  if (isBanner && typeof document !== "undefined" && document.body) {
1244
1246
  try {
1245
1247
  const isBottom = design.position === "bottom";
@@ -1261,14 +1263,64 @@ function renderCampaignDOM(campaign, handlers = {}) {
1261
1263
  }
1262
1264
  } catch {
1263
1265
  }
1266
+ const findTopFixedElements = () => {
1267
+ const candidates = [];
1268
+ try {
1269
+ const elements = Array.from(
1270
+ document.querySelectorAll(
1271
+ 'header, nav, [role="banner"], [class*="header"], [class*="navbar"], [class*="nav-fixed"], [class*="sticky"]'
1272
+ )
1273
+ );
1274
+ Array.from(document.body.children).forEach((child) => {
1275
+ if (child instanceof HTMLElement && child !== wrapper && child !== overlayEl && !elements.includes(child)) {
1276
+ elements.push(child);
1277
+ }
1278
+ });
1279
+ for (const el of elements) {
1280
+ if (el === wrapper || el === overlayEl || !el.isConnected) continue;
1281
+ const style = window.getComputedStyle(el);
1282
+ const pos = style.position;
1283
+ if (pos === "fixed" || pos === "sticky") {
1284
+ const rect = el.getBoundingClientRect();
1285
+ if (rect.top <= 15 && rect.height > 0 && rect.height < window.innerHeight * 0.5) {
1286
+ candidates.push(el);
1287
+ }
1288
+ }
1289
+ }
1290
+ } catch {
1291
+ }
1292
+ return candidates;
1293
+ };
1264
1294
  const updateOffset = () => {
1265
1295
  if (!wrapper || !document.body) return;
1266
1296
  const bannerHeight = wrapper.offsetHeight;
1267
1297
  if (bannerHeight > 0) {
1268
1298
  if (isBottom) {
1269
1299
  document.body.style.paddingBottom = `${basePaddingBottom + bannerHeight}px`;
1300
+ document.documentElement.style.setProperty(
1301
+ "--sitepulse-bottom-banner-height",
1302
+ `${bannerHeight}px`
1303
+ );
1270
1304
  } else {
1271
1305
  document.body.style.paddingTop = `${basePaddingTop + bannerHeight}px`;
1306
+ document.documentElement.style.setProperty(
1307
+ "--sitepulse-top-banner-height",
1308
+ `${bannerHeight}px`
1309
+ );
1310
+ document.documentElement.classList.add("has-sitepulse-top-banner");
1311
+ const fixedHeaders = findTopFixedElements();
1312
+ for (const el of fixedHeaders) {
1313
+ if (!shiftedElements.some((r) => r.el === el)) {
1314
+ shiftedElements.push({
1315
+ el,
1316
+ originalTop: el.style.top,
1317
+ originalTransform: el.style.transform,
1318
+ originalTransition: el.style.transition
1319
+ });
1320
+ el.style.transition = el.style.transition ? `${el.style.transition}, transform 0.2s ease` : "transform 0.2s ease";
1321
+ el.style.transform = `translateY(${bannerHeight}px)`;
1322
+ }
1323
+ }
1272
1324
  }
1273
1325
  }
1274
1326
  };
@@ -1301,6 +1353,23 @@ function renderCampaignDOM(campaign, handlers = {}) {
1301
1353
  if (originalPaddingBottom !== null) {
1302
1354
  document.body.style.paddingBottom = originalPaddingBottom;
1303
1355
  }
1356
+ document.documentElement.style.setProperty(
1357
+ "--sitepulse-top-banner-height",
1358
+ "0px"
1359
+ );
1360
+ document.documentElement.style.setProperty(
1361
+ "--sitepulse-bottom-banner-height",
1362
+ "0px"
1363
+ );
1364
+ document.documentElement.classList.remove("has-sitepulse-top-banner");
1365
+ for (const record of shiftedElements) {
1366
+ if (record.el && record.el.isConnected) {
1367
+ record.el.style.transform = record.originalTransform;
1368
+ record.el.style.transition = record.originalTransition;
1369
+ record.el.style.top = record.originalTop;
1370
+ }
1371
+ }
1372
+ shiftedElements.length = 0;
1304
1373
  } catch {
1305
1374
  }
1306
1375
  }
@@ -1456,9 +1525,18 @@ var SitePulseClient = class {
1456
1525
  registeredCampaignTeardowns = [];
1457
1526
  init(config) {
1458
1527
  const isReinit = this.initialized;
1528
+ let inferredBase = "";
1529
+ if (typeof document !== "undefined" && document.currentScript?.src) {
1530
+ try {
1531
+ inferredBase = new URL(document.currentScript.src).origin;
1532
+ } catch {
1533
+ }
1534
+ }
1535
+ const defaultHost = config.apiHost || inferredBase || (typeof window !== "undefined" && window.location.hostname === "localhost" && window.location.port === "3000" ? "" : "https://sitepulse-app-ten.vercel.app");
1459
1536
  this.config = {
1460
- endpointUrl: "/api/collect",
1461
- campaignsEndpointUrl: "/api/campaigns/active",
1537
+ endpointUrl: defaultHost ? `${defaultHost}/api/collect` : "/api/collect",
1538
+ campaignsEndpointUrl: defaultHost ? `${defaultHost}/api/campaigns/active` : "/api/campaigns/active",
1539
+ submissionsEndpointUrl: defaultHost ? `${defaultHost}/api/campaigns/submit` : "/api/campaigns/submit",
1462
1540
  flushIntervalMs: 5e3,
1463
1541
  autoTrack: true,
1464
1542
  autoFetchCampaigns: true,
@@ -1617,6 +1695,7 @@ var SitePulseClient = class {
1617
1695
  campaignName: campaign.name
1618
1696
  });
1619
1697
  const dom = renderCampaignDOM(campaign, {
1698
+ submissionsEndpointUrl: handlers.submissionsEndpointUrl || this.config?.submissionsEndpointUrl,
1620
1699
  onLinkClick: (url, eventName) => {
1621
1700
  this.track("campaign_click", {
1622
1701
  campaignId: campaign.id,
package/dist/index.mjs CHANGED
@@ -1013,7 +1013,8 @@ function renderCampaignDOM(campaign, handlers = {}) {
1013
1013
  const emailVal = entries.email || entries.email_address || entries.newsletter_email || null;
1014
1014
  handlers.onSubmit?.(entries);
1015
1015
  if (typeof fetch !== "undefined") {
1016
- fetch("/api/campaigns/submit", {
1016
+ const submitEndpoint = handlers.submissionsEndpointUrl || "/api/campaigns/submit";
1017
+ fetch(submitEndpoint, {
1017
1018
  method: "POST",
1018
1019
  headers: { "Content-Type": "application/json" },
1019
1020
  body: JSON.stringify({
@@ -1201,6 +1202,7 @@ function renderCampaignDOM(campaign, handlers = {}) {
1201
1202
  let originalPaddingTop = null;
1202
1203
  let originalPaddingBottom = null;
1203
1204
  let resizeObserver = null;
1205
+ const shiftedElements = [];
1204
1206
  if (isBanner && typeof document !== "undefined" && document.body) {
1205
1207
  try {
1206
1208
  const isBottom = design.position === "bottom";
@@ -1222,14 +1224,64 @@ function renderCampaignDOM(campaign, handlers = {}) {
1222
1224
  }
1223
1225
  } catch {
1224
1226
  }
1227
+ const findTopFixedElements = () => {
1228
+ const candidates = [];
1229
+ try {
1230
+ const elements = Array.from(
1231
+ document.querySelectorAll(
1232
+ 'header, nav, [role="banner"], [class*="header"], [class*="navbar"], [class*="nav-fixed"], [class*="sticky"]'
1233
+ )
1234
+ );
1235
+ Array.from(document.body.children).forEach((child) => {
1236
+ if (child instanceof HTMLElement && child !== wrapper && child !== overlayEl && !elements.includes(child)) {
1237
+ elements.push(child);
1238
+ }
1239
+ });
1240
+ for (const el of elements) {
1241
+ if (el === wrapper || el === overlayEl || !el.isConnected) continue;
1242
+ const style = window.getComputedStyle(el);
1243
+ const pos = style.position;
1244
+ if (pos === "fixed" || pos === "sticky") {
1245
+ const rect = el.getBoundingClientRect();
1246
+ if (rect.top <= 15 && rect.height > 0 && rect.height < window.innerHeight * 0.5) {
1247
+ candidates.push(el);
1248
+ }
1249
+ }
1250
+ }
1251
+ } catch {
1252
+ }
1253
+ return candidates;
1254
+ };
1225
1255
  const updateOffset = () => {
1226
1256
  if (!wrapper || !document.body) return;
1227
1257
  const bannerHeight = wrapper.offsetHeight;
1228
1258
  if (bannerHeight > 0) {
1229
1259
  if (isBottom) {
1230
1260
  document.body.style.paddingBottom = `${basePaddingBottom + bannerHeight}px`;
1261
+ document.documentElement.style.setProperty(
1262
+ "--sitepulse-bottom-banner-height",
1263
+ `${bannerHeight}px`
1264
+ );
1231
1265
  } else {
1232
1266
  document.body.style.paddingTop = `${basePaddingTop + bannerHeight}px`;
1267
+ document.documentElement.style.setProperty(
1268
+ "--sitepulse-top-banner-height",
1269
+ `${bannerHeight}px`
1270
+ );
1271
+ document.documentElement.classList.add("has-sitepulse-top-banner");
1272
+ const fixedHeaders = findTopFixedElements();
1273
+ for (const el of fixedHeaders) {
1274
+ if (!shiftedElements.some((r) => r.el === el)) {
1275
+ shiftedElements.push({
1276
+ el,
1277
+ originalTop: el.style.top,
1278
+ originalTransform: el.style.transform,
1279
+ originalTransition: el.style.transition
1280
+ });
1281
+ el.style.transition = el.style.transition ? `${el.style.transition}, transform 0.2s ease` : "transform 0.2s ease";
1282
+ el.style.transform = `translateY(${bannerHeight}px)`;
1283
+ }
1284
+ }
1233
1285
  }
1234
1286
  }
1235
1287
  };
@@ -1262,6 +1314,23 @@ function renderCampaignDOM(campaign, handlers = {}) {
1262
1314
  if (originalPaddingBottom !== null) {
1263
1315
  document.body.style.paddingBottom = originalPaddingBottom;
1264
1316
  }
1317
+ document.documentElement.style.setProperty(
1318
+ "--sitepulse-top-banner-height",
1319
+ "0px"
1320
+ );
1321
+ document.documentElement.style.setProperty(
1322
+ "--sitepulse-bottom-banner-height",
1323
+ "0px"
1324
+ );
1325
+ document.documentElement.classList.remove("has-sitepulse-top-banner");
1326
+ for (const record of shiftedElements) {
1327
+ if (record.el && record.el.isConnected) {
1328
+ record.el.style.transform = record.originalTransform;
1329
+ record.el.style.transition = record.originalTransition;
1330
+ record.el.style.top = record.originalTop;
1331
+ }
1332
+ }
1333
+ shiftedElements.length = 0;
1265
1334
  } catch {
1266
1335
  }
1267
1336
  }
@@ -1417,9 +1486,18 @@ var SitePulseClient = class {
1417
1486
  registeredCampaignTeardowns = [];
1418
1487
  init(config) {
1419
1488
  const isReinit = this.initialized;
1489
+ let inferredBase = "";
1490
+ if (typeof document !== "undefined" && document.currentScript?.src) {
1491
+ try {
1492
+ inferredBase = new URL(document.currentScript.src).origin;
1493
+ } catch {
1494
+ }
1495
+ }
1496
+ const defaultHost = config.apiHost || inferredBase || (typeof window !== "undefined" && window.location.hostname === "localhost" && window.location.port === "3000" ? "" : "https://sitepulse-app-ten.vercel.app");
1420
1497
  this.config = {
1421
- endpointUrl: "/api/collect",
1422
- campaignsEndpointUrl: "/api/campaigns/active",
1498
+ endpointUrl: defaultHost ? `${defaultHost}/api/collect` : "/api/collect",
1499
+ campaignsEndpointUrl: defaultHost ? `${defaultHost}/api/campaigns/active` : "/api/campaigns/active",
1500
+ submissionsEndpointUrl: defaultHost ? `${defaultHost}/api/campaigns/submit` : "/api/campaigns/submit",
1423
1501
  flushIntervalMs: 5e3,
1424
1502
  autoTrack: true,
1425
1503
  autoFetchCampaigns: true,
@@ -1578,6 +1656,7 @@ var SitePulseClient = class {
1578
1656
  campaignName: campaign.name
1579
1657
  });
1580
1658
  const dom = renderCampaignDOM(campaign, {
1659
+ submissionsEndpointUrl: handlers.submissionsEndpointUrl || this.config?.submissionsEndpointUrl,
1581
1660
  onLinkClick: (url, eventName) => {
1582
1661
  this.track("campaign_click", {
1583
1662
  campaignId: campaign.id,
@@ -14,6 +14,7 @@ export interface CampaignActionHandlers {
14
14
  onEventClick?: (eventName: string) => void;
15
15
  onSubmit?: (formData: Record<string, string>) => void;
16
16
  onClose?: () => void;
17
+ submissionsEndpointUrl?: string;
17
18
  }
18
19
  export declare function renderHeadingBlock(block: Extract<ContentBlock, {
19
20
  type: "heading";
@@ -4,8 +4,10 @@ export * from "./renderer";
4
4
  export * from "./evaluator";
5
5
  export interface SitePulseConfig {
6
6
  siteId: string;
7
+ apiHost?: string;
7
8
  endpointUrl?: string;
8
9
  campaignsEndpointUrl?: string;
10
+ submissionsEndpointUrl?: string;
9
11
  flushIntervalMs?: number;
10
12
  autoTrack?: boolean;
11
13
  autoFetchCampaigns?: boolean;
@@ -14,6 +14,7 @@ export interface CampaignActionHandlers {
14
14
  onEventClick?: (eventName: string) => void;
15
15
  onSubmit?: (formData: Record<string, string>) => void;
16
16
  onClose?: () => void;
17
+ submissionsEndpointUrl?: string;
17
18
  }
18
19
  export declare function renderHeadingBlock(block: Extract<ContentBlock, {
19
20
  type: "heading";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitepulse/web",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Official SitePulse Web SDK for telemetry, visitor analytics, and on-site engagement",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",