@pushwoosh/websdk-common 6.17.1 → 6.18.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/websdk-common",
3
- "version": "6.17.1",
3
+ "version": "6.18.0",
4
4
  "description": "Code shared by the Pushwoosh WebSDK and the Control Panel, one entry point per module (web-popups: web popup model and renderer; subscription-widget: push subscription widget model and renderer)",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -9,7 +9,7 @@ export declare function SubscriptionPromptBox({ config, variant, colors, busy, o
9
9
  onAccept?: () => void;
10
10
  onDecline?: () => void;
11
11
  }): import("react").JSX.Element;
12
- /** The launcher bell without placement. */
12
+ /** The launcher without placement: the authored picture, or the round bell button. */
13
13
  export declare function SubscriptionLauncherButton({ config, blocked, colors, onClick }: {
14
14
  config: SubscriptionWidgetConfig;
15
15
  blocked: boolean;
@@ -17,7 +17,7 @@ export declare function SubscriptionLauncherButton({ config, blocked, colors, on
17
17
  onClick?: () => void;
18
18
  }): import("react").JSX.Element;
19
19
  /**
20
- * The launcher, or while `open` the prompt in its place; 'live' pins it to the viewport, 'preview' to the
21
- * nearest positioned parent with no enter animation. The host owns `open` and every side effect.
20
+ * The prompt while `open` and the launcher, each only when switched on; 'live' pins them to the viewport,
21
+ * 'preview' to the nearest positioned parent with no enter animation. The host owns `open` and every side effect.
22
22
  */
23
23
  export declare function SubscriptionWidgetReader({ config, permission, open, mode, colorScheme, busy, onAccept, onDecline, onLauncherClick, }: SubscriptionWidgetReaderProps): import("react").JSX.Element | null;
@@ -195,7 +195,7 @@ export function SubscriptionPromptBox({
195
195
  })]
196
196
  });
197
197
  }
198
- /** The launcher bell without placement. */
198
+ /** The launcher without placement: the authored picture, or the round bell button. */
199
199
  export function SubscriptionLauncherButton({
200
200
  config,
201
201
  blocked,
@@ -203,29 +203,56 @@ export function SubscriptionLauncherButton({
203
203
  onClick
204
204
  }) {
205
205
  const {
206
- size
206
+ width,
207
+ height,
208
+ imageUrl
207
209
  } = config.launcher;
208
210
  const label = blocked ? config.texts.launcherBlockedLabel : config.texts.launcherLabel;
209
- const iconSize = Math.round(size / 2);
211
+ const iconSize = Math.round(Math.min(width, height) / 2);
212
+ const base = {
213
+ display: 'flex',
214
+ alignItems: 'center',
215
+ justifyContent: 'center',
216
+ width,
217
+ height,
218
+ padding: 0,
219
+ border: 'none',
220
+ cursor: 'pointer',
221
+ opacity: blocked ? 0.7 : 1
222
+ };
223
+ if (imageUrl) {
224
+ return _jsx("button", {
225
+ type: "button",
226
+ "aria-label": label,
227
+ title: label,
228
+ onClick: onClick,
229
+ style: {
230
+ ...base,
231
+ background: 'none'
232
+ },
233
+ children: _jsx("img", {
234
+ src: imageUrl,
235
+ alt: "",
236
+ width: width,
237
+ height: height,
238
+ style: {
239
+ display: 'block',
240
+ objectFit: 'contain'
241
+ }
242
+ })
243
+ });
244
+ }
210
245
  return _jsx("button", {
211
246
  type: "button",
212
247
  "aria-label": label,
213
248
  title: label,
214
249
  onClick: onClick,
215
250
  style: {
216
- display: 'flex',
217
- alignItems: 'center',
218
- justifyContent: 'center',
219
- width: size,
220
- height: size,
221
- padding: 0,
222
- border: 'none',
251
+ ...base,
223
252
  borderRadius: '50%',
224
- backgroundColor: colors.accent,
225
- color: colors.onAccent,
226
- boxShadow: BOX_SHADOW,
227
- cursor: 'pointer',
228
- opacity: blocked ? 0.7 : 1
253
+ backgroundColor: config.launcher.background ?? colors.accent,
254
+ color: config.launcher.iconColor ?? colors.onAccent,
255
+ boxShadow: BOX_SHADOW
229
256
  },
230
257
  children: _jsxs("svg", {
231
258
  width: iconSize,
@@ -246,8 +273,8 @@ export function SubscriptionLauncherButton({
246
273
  });
247
274
  }
248
275
  /**
249
- * The launcher, or while `open` the prompt in its place; 'live' pins it to the viewport, 'preview' to the
250
- * nearest positioned parent with no enter animation. The host owns `open` and every side effect.
276
+ * The prompt while `open` and the launcher, each only when switched on; 'live' pins them to the viewport,
277
+ * 'preview' to the nearest positioned parent with no enter animation. The host owns `open` and every side effect.
251
278
  */
252
279
  export function SubscriptionWidgetReader({
253
280
  config,
@@ -264,25 +291,13 @@ export function SubscriptionWidgetReader({
264
291
  const colors = resolveWebPopupColors(config, scheme);
265
292
  const live = mode === 'live';
266
293
  const position = live ? 'fixed' : 'absolute';
267
- const launcher = !open && subscriptionWidgetLauncherVisible(config, permission);
268
- if (!config.enable || !open && !launcher) return null;
294
+ const prompt = open && config.prompt.enabled;
295
+ const launcher = subscriptionWidgetLauncherVisible(config, permission);
296
+ if (!prompt && !launcher) return null;
269
297
  return _jsxs(_Fragment, {
270
298
  children: [live && _jsx("style", {
271
299
  children: SUBSCRIPTION_WIDGET_KEYFRAMES
272
- }), launcher && _jsx("div", {
273
- style: {
274
- position,
275
- zIndex: config.zIndex,
276
- pointerEvents: 'auto',
277
- ...cornerStyle(config.launcher.position, config.launcher.offset)
278
- },
279
- children: _jsx(SubscriptionLauncherButton, {
280
- config: config,
281
- blocked: permission === 'denied',
282
- colors: colors,
283
- onClick: onLauncherClick
284
- })
285
- }), open && config.prompt.overlay && _jsx("div", {
300
+ }), prompt && config.prompt.overlay && _jsx("div", {
286
301
  style: {
287
302
  position,
288
303
  inset: 0,
@@ -292,7 +307,7 @@ export function SubscriptionWidgetReader({
292
307
  pointerEvents: 'auto'
293
308
  },
294
309
  onClick: onDecline
295
- }), open && _jsx("div", {
310
+ }), prompt && _jsx("div", {
296
311
  style: promptFrameStyle(config, position, live),
297
312
  children: _jsx(SubscriptionPromptBox, {
298
313
  config: config,
@@ -302,6 +317,19 @@ export function SubscriptionWidgetReader({
302
317
  onAccept: onAccept,
303
318
  onDecline: onDecline
304
319
  })
320
+ }), launcher && _jsx("div", {
321
+ style: {
322
+ position,
323
+ zIndex: config.zIndex,
324
+ pointerEvents: 'auto',
325
+ ...cornerStyle(config.launcher.position, config.launcher.offset)
326
+ },
327
+ children: _jsx(SubscriptionLauncherButton, {
328
+ config: config,
329
+ blocked: permission === 'denied',
330
+ colors: colors,
331
+ onClick: onLauncherClick
332
+ })
305
333
  })]
306
334
  });
307
335
  }
@@ -11,9 +11,10 @@ export declare const SUBSCRIPTION_WIDGET_LIMITS: {
11
11
  readonly cappingInterval: readonly [0, 8760];
12
12
  readonly borderRadius: readonly [0, 32];
13
13
  readonly offset: readonly [0, 100];
14
- readonly size: readonly [32, 80];
14
+ readonly launcherSize: readonly [16, 200];
15
15
  readonly zIndex: readonly [0, 2147483647];
16
16
  };
17
17
  export declare const SUBSCRIPTION_WIDGET_TEXT_MAX_LENGTH = 300;
18
+ export declare const SUBSCRIPTION_WIDGET_COLOR_MAX_LENGTH = 64;
18
19
  export declare const SUBSCRIPTION_WIDGET_DEFAULT_CONFIG: SubscriptionWidgetConfig;
19
20
  export declare const SUBSCRIPTION_WIDGET_KEYFRAMES = "\n@keyframes pw-subw-fade { from { opacity: 0; } }\n@keyframes pw-subw-drop { from { opacity: 0; translate: 0 -16px; } }\n@keyframes pw-subw-rise { from { opacity: 0; translate: 0 16px; } }\n";
@@ -15,13 +15,15 @@ export const SUBSCRIPTION_WIDGET_LIMITS = {
15
15
  cappingInterval: [0, 8760],
16
16
  borderRadius: [0, 32],
17
17
  offset: [0, 100],
18
- size: [32, 80],
18
+ launcherSize: [16, 200],
19
19
  zIndex: [0, 2147483647]
20
20
  };
21
21
  export const SUBSCRIPTION_WIDGET_TEXT_MAX_LENGTH = 300;
22
+ export const SUBSCRIPTION_WIDGET_COLOR_MAX_LENGTH = 64;
22
23
  export const SUBSCRIPTION_WIDGET_DEFAULT_CONFIG = {
23
24
  enable: false,
24
25
  prompt: {
26
+ enabled: false,
25
27
  layout: 'card',
26
28
  position: 'top',
27
29
  borderRadius: 12,
@@ -31,7 +33,8 @@ export const SUBSCRIPTION_WIDGET_DEFAULT_CONFIG = {
31
33
  enabled: false,
32
34
  position: 'bottom-right',
33
35
  offset: 20,
34
- size: 48
36
+ width: 48,
37
+ height: 48
35
38
  },
36
39
  display: {
37
40
  trigger: 'auto',
@@ -1,4 +1,4 @@
1
- import { type SubscriptionWidgetConfig, type SubscriptionWidgetHistory, type SubscriptionWidgetPermission, type SubscriptionWidgetPromptVariant } from './types';
1
+ import { type SubscriptionWidgetConfig, type SubscriptionWidgetHistory, type SubscriptionWidgetLauncherAction, type SubscriptionWidgetPermission, type SubscriptionWidgetPromptVariant } from './types';
2
2
  /**
3
3
  * The widget configuration from API-writable json: enums whitelisted, numbers
4
4
  * clamped, strings trimmed and capped, urls sanitized, defaults for the rest.
@@ -10,6 +10,8 @@ export declare function subscriptionWidgetActionable(permission: SubscriptionWid
10
10
  export declare function subscriptionWidgetPromptVariant(permission: SubscriptionWidgetPermission): SubscriptionWidgetPromptVariant;
11
11
  /** Whether the launcher is on screen. */
12
12
  export declare function subscriptionWidgetLauncherVisible(config: SubscriptionWidgetConfig, permission: SubscriptionWidgetPermission): boolean;
13
+ /** What a launcher click does for this visitor. */
14
+ export declare function subscriptionWidgetLauncherAction(config: SubscriptionWidgetConfig, permission: SubscriptionWidgetPermission): SubscriptionWidgetLauncherAction;
13
15
  /** Whether the prompt may open by itself now; the host still waits `display.delay` before showing it. */
14
16
  export declare function subscriptionWidgetCanAutoShow(config: SubscriptionWidgetConfig, permission: SubscriptionWidgetPermission, history: SubscriptionWidgetHistory, nowMs: number): boolean;
15
17
  /** The history after one more auto show at `nowMs`. */
@@ -1,4 +1,4 @@
1
- import { SUBSCRIPTION_WIDGET_CORNERS, SUBSCRIPTION_WIDGET_DEFAULT_CONFIG, SUBSCRIPTION_WIDGET_LAYOUTS, SUBSCRIPTION_WIDGET_LIMITS, SUBSCRIPTION_WIDGET_POSITIONS, SUBSCRIPTION_WIDGET_TEXT_MAX_LENGTH, SUBSCRIPTION_WIDGET_TRIGGERS } from './constants';
1
+ import { SUBSCRIPTION_WIDGET_CORNERS, SUBSCRIPTION_WIDGET_DEFAULT_CONFIG, SUBSCRIPTION_WIDGET_LAYOUTS, SUBSCRIPTION_WIDGET_LIMITS, SUBSCRIPTION_WIDGET_POSITIONS, SUBSCRIPTION_WIDGET_TEXT_MAX_LENGTH, SUBSCRIPTION_WIDGET_TRIGGERS, SUBSCRIPTION_WIDGET_COLOR_MAX_LENGTH } from './constants';
2
2
  import { normalizeWebPopupColors, normalizeWebPopupDark, safeUrl } from '../web-popups/helpers';
3
3
  const HOUR_MS = 3600 * 1000;
4
4
  function record(value) {
@@ -20,6 +20,12 @@ function imageUrl(value) {
20
20
  const url = safeUrl(typeof value === 'string' ? value : undefined);
21
21
  return url && /^(https?:\/\/|\/)/i.test(url) ? url : undefined;
22
22
  }
23
+ function optionalColor(key, value) {
24
+ const color = typeof value === 'string' ? value.trim().slice(0, SUBSCRIPTION_WIDGET_COLOR_MAX_LENGTH) : '';
25
+ return color ? {
26
+ [key]: color
27
+ } : {};
28
+ }
23
29
  function normalizeTexts(raw) {
24
30
  const defaults = SUBSCRIPTION_WIDGET_DEFAULT_CONFIG.texts;
25
31
  const keys = Object.keys(defaults);
@@ -40,15 +46,19 @@ export function normalizeSubscriptionWidgetConfig(raw) {
40
46
  const positions = SUBSCRIPTION_WIDGET_POSITIONS[layout];
41
47
  const trigger = oneOf(display.trigger, SUBSCRIPTION_WIDGET_TRIGGERS, defaults.display.trigger);
42
48
  const iconUrl = imageUrl(prompt.iconUrl);
49
+ const launcherImageUrl = imageUrl(launcher.imageUrl);
43
50
  const colors = normalizeWebPopupColors({
44
51
  colors: config.colors
45
52
  });
46
53
  const dark = normalizeWebPopupDark({
47
54
  dark: config.dark
48
55
  }, colors);
56
+ const promptEnabled = prompt.enabled === true;
57
+ const launcherEnabled = launcher.enabled === true;
49
58
  return {
50
- enable: config.enable === true,
59
+ enable: promptEnabled || launcherEnabled,
51
60
  prompt: {
61
+ enabled: promptEnabled,
52
62
  layout,
53
63
  position: oneOf(prompt.position, positions, positions[0]),
54
64
  ...(iconUrl ? {
@@ -58,10 +68,16 @@ export function normalizeSubscriptionWidgetConfig(raw) {
58
68
  overlay: layout === 'modal' || prompt.overlay === true
59
69
  },
60
70
  launcher: {
61
- enabled: trigger === 'launcher' || launcher.enabled === true,
71
+ enabled: launcherEnabled,
62
72
  position: oneOf(launcher.position, SUBSCRIPTION_WIDGET_CORNERS, defaults.launcher.position),
63
73
  offset: clamped(launcher.offset, limits.offset, defaults.launcher.offset),
64
- size: clamped(launcher.size, limits.size, defaults.launcher.size)
74
+ width: clamped(launcher.width, limits.launcherSize, defaults.launcher.width),
75
+ height: clamped(launcher.height, limits.launcherSize, defaults.launcher.height),
76
+ ...(launcherImageUrl ? {
77
+ imageUrl: launcherImageUrl
78
+ } : {}),
79
+ ...optionalColor('background', launcher.background),
80
+ ...optionalColor('iconColor', launcher.iconColor)
65
81
  },
66
82
  display: {
67
83
  trigger,
@@ -88,7 +104,12 @@ export function subscriptionWidgetPromptVariant(permission) {
88
104
  }
89
105
  /** Whether the launcher is on screen. */
90
106
  export function subscriptionWidgetLauncherVisible(config, permission) {
91
- return config.enable && config.launcher.enabled && subscriptionWidgetActionable(permission);
107
+ return config.launcher.enabled && subscriptionWidgetActionable(permission);
108
+ }
109
+ /** What a launcher click does for this visitor. */
110
+ export function subscriptionWidgetLauncherAction(config, permission) {
111
+ if (config.prompt.enabled) return 'prompt';
112
+ return permission === 'default' ? 'subscribe' : 'none';
92
113
  }
93
114
  /** Whether the prompt may open by itself now; the host still waits `display.delay` before showing it. */
94
115
  export function subscriptionWidgetCanAutoShow(config, permission, history, nowMs) {
@@ -97,7 +118,7 @@ export function subscriptionWidgetCanAutoShow(config, permission, history, nowMs
97
118
  cappingCount,
98
119
  cappingInterval
99
120
  } = config.display;
100
- if (!config.enable || trigger !== 'auto' || permission !== 'default') return false;
121
+ if (!config.prompt.enabled || trigger !== 'auto' || permission !== 'default') return false;
101
122
  if (cappingCount > 0 && history.shownCount >= cappingCount) return false;
102
123
  return history.lastShownAt === null || nowMs - history.lastShownAt >= cappingInterval * HOUR_MS;
103
124
  }
@@ -1,4 +1,4 @@
1
1
  export { SubscriptionWidgetReader, SubscriptionPromptBox, SubscriptionLauncherButton } from './SubscriptionWidgetReader';
2
- export { normalizeSubscriptionWidgetConfig, subscriptionWidgetActionable, subscriptionWidgetPromptVariant, subscriptionWidgetLauncherVisible, subscriptionWidgetCanAutoShow, nextSubscriptionWidgetHistory, } from './helpers';
3
- export { SUBSCRIPTION_WIDGET_DEFAULT_CONFIG, SUBSCRIPTION_WIDGET_POSITIONS, SUBSCRIPTION_WIDGET_LAYOUTS, SUBSCRIPTION_WIDGET_CORNERS, SUBSCRIPTION_WIDGET_TRIGGERS, SUBSCRIPTION_WIDGET_LIMITS, SUBSCRIPTION_WIDGET_TEXT_MAX_LENGTH, } from './constants';
4
- export type { SubscriptionWidgetConfig, SubscriptionWidgetCorner, SubscriptionWidgetDisplay, SubscriptionWidgetHistory, SubscriptionWidgetLauncher, SubscriptionWidgetLayout, SubscriptionWidgetPermission, SubscriptionWidgetPosition, SubscriptionWidgetPrompt, SubscriptionWidgetPromptVariant, SubscriptionWidgetReaderMode, SubscriptionWidgetReaderProps, SubscriptionWidgetTexts, SubscriptionWidgetTrigger, } from './types';
2
+ export { normalizeSubscriptionWidgetConfig, subscriptionWidgetActionable, subscriptionWidgetPromptVariant, subscriptionWidgetLauncherVisible, subscriptionWidgetLauncherAction, subscriptionWidgetCanAutoShow, nextSubscriptionWidgetHistory, } from './helpers';
3
+ export { SUBSCRIPTION_WIDGET_DEFAULT_CONFIG, SUBSCRIPTION_WIDGET_POSITIONS, SUBSCRIPTION_WIDGET_LAYOUTS, SUBSCRIPTION_WIDGET_CORNERS, SUBSCRIPTION_WIDGET_TRIGGERS, SUBSCRIPTION_WIDGET_LIMITS, SUBSCRIPTION_WIDGET_TEXT_MAX_LENGTH, SUBSCRIPTION_WIDGET_COLOR_MAX_LENGTH, } from './constants';
4
+ export type { SubscriptionWidgetConfig, SubscriptionWidgetCorner, SubscriptionWidgetDisplay, SubscriptionWidgetHistory, SubscriptionWidgetLauncher, SubscriptionWidgetLauncherAction, SubscriptionWidgetLayout, SubscriptionWidgetPermission, SubscriptionWidgetPosition, SubscriptionWidgetPrompt, SubscriptionWidgetPromptVariant, SubscriptionWidgetReaderMode, SubscriptionWidgetReaderProps, SubscriptionWidgetTexts, SubscriptionWidgetTrigger, } from './types';
@@ -1,3 +1,3 @@
1
1
  export { SubscriptionWidgetReader, SubscriptionPromptBox, SubscriptionLauncherButton } from './SubscriptionWidgetReader';
2
- export { normalizeSubscriptionWidgetConfig, subscriptionWidgetActionable, subscriptionWidgetPromptVariant, subscriptionWidgetLauncherVisible, subscriptionWidgetCanAutoShow, nextSubscriptionWidgetHistory } from './helpers';
3
- export { SUBSCRIPTION_WIDGET_DEFAULT_CONFIG, SUBSCRIPTION_WIDGET_POSITIONS, SUBSCRIPTION_WIDGET_LAYOUTS, SUBSCRIPTION_WIDGET_CORNERS, SUBSCRIPTION_WIDGET_TRIGGERS, SUBSCRIPTION_WIDGET_LIMITS, SUBSCRIPTION_WIDGET_TEXT_MAX_LENGTH } from './constants';
2
+ export { normalizeSubscriptionWidgetConfig, subscriptionWidgetActionable, subscriptionWidgetPromptVariant, subscriptionWidgetLauncherVisible, subscriptionWidgetLauncherAction, subscriptionWidgetCanAutoShow, nextSubscriptionWidgetHistory } from './helpers';
3
+ export { SUBSCRIPTION_WIDGET_DEFAULT_CONFIG, SUBSCRIPTION_WIDGET_POSITIONS, SUBSCRIPTION_WIDGET_LAYOUTS, SUBSCRIPTION_WIDGET_CORNERS, SUBSCRIPTION_WIDGET_TRIGGERS, SUBSCRIPTION_WIDGET_LIMITS, SUBSCRIPTION_WIDGET_TEXT_MAX_LENGTH, SUBSCRIPTION_WIDGET_COLOR_MAX_LENGTH } from './constants';
@@ -28,6 +28,8 @@ export type SubscriptionWidgetTexts = {
28
28
  };
29
29
  /** The ask itself. */
30
30
  export type SubscriptionWidgetPrompt = {
31
+ /** Whether the prompt is drawn at all; independent of the launcher. */
32
+ enabled: boolean;
31
33
  layout: SubscriptionWidgetLayout;
32
34
  position: SubscriptionWidgetPosition;
33
35
  /** Image drawn beside the copy; absent = no image. */
@@ -36,15 +38,25 @@ export type SubscriptionWidgetPrompt = {
36
38
  /** Page dim behind the prompt; a modal always has one. */
37
39
  overlay: boolean;
38
40
  };
39
- /** The floating bell that reopens the prompt. */
41
+ /** The floating bell: opens the prompt when it is on, asks for the permission itself otherwise. */
40
42
  export type SubscriptionWidgetLauncher = {
43
+ /** Whether the bell is drawn at all; independent of the prompt. */
41
44
  enabled: boolean;
42
45
  position: SubscriptionWidgetCorner;
43
46
  /** Distance from the screen edges, px. */
44
47
  offset: number;
45
- /** Button diameter, px. */
46
- size: number;
48
+ /** Button size, px. */
49
+ width: number;
50
+ height: number;
51
+ /** Picture drawn instead of the round bell button; absent = the bell. */
52
+ imageUrl?: string;
53
+ /** Fill of the round button; absent = the palette accent. */
54
+ background?: string;
55
+ /** The bell glyph; absent = the label colour over the accent. */
56
+ iconColor?: string;
47
57
  };
58
+ /** What a launcher click does: open the prompt, ask the browser directly, or nothing (blocked). */
59
+ export type SubscriptionWidgetLauncherAction = 'prompt' | 'subscribe' | 'none';
48
60
  /** When the prompt shows by itself. */
49
61
  export type SubscriptionWidgetDisplay = {
50
62
  trigger: SubscriptionWidgetTrigger;
@@ -57,6 +69,7 @@ export type SubscriptionWidgetDisplay = {
57
69
  };
58
70
  /** The whole widget configuration as stored in the WebSDK config (`subscriptionWidget`). */
59
71
  export type SubscriptionWidgetConfig = {
72
+ /** True when the prompt or the launcher is on, i.e. the widget draws something. */
60
73
  enable: boolean;
61
74
  prompt: SubscriptionWidgetPrompt;
62
75
  launcher: SubscriptionWidgetLauncher;