@preprio/toolkit 0.1.0-beta.4 → 0.1.0-beta.6

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.
@@ -0,0 +1,7 @@
1
+ // src/core/json-script.ts
2
+ function serializeForScriptTag(value) {
3
+ return JSON.stringify(value ?? null).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
4
+ }
5
+ export {
6
+ serializeForScriptTag
7
+ };
@@ -169,7 +169,11 @@ function t(key, locale, vars) {
169
169
  }
170
170
 
171
171
  // src/core/utils.ts
172
- function sendPreprEvent(event, data) {
172
+ var trustedParentOrigin = null;
173
+ function setTrustedParentOrigin(origin) {
174
+ trustedParentOrigin = origin;
175
+ }
176
+ function sendPreprEvent(event, data, options) {
173
177
  if (typeof window !== "undefined") {
174
178
  const message = {
175
179
  name: "prepr_preview_bar",
@@ -180,7 +184,11 @@ function sendPreprEvent(event, data) {
180
184
  new CustomEvent("prepr_preview_bar", { detail: message })
181
185
  );
182
186
  if (window.parent && window.parent !== window) {
183
- window.parent.postMessage(message, "*");
187
+ if (trustedParentOrigin) {
188
+ window.parent.postMessage(message, trustedParentOrigin);
189
+ } else if (options?.allowUntrustedTarget) {
190
+ window.parent.postMessage(message, "*");
191
+ }
184
192
  }
185
193
  }
186
194
  }
@@ -266,7 +274,23 @@ function createElementCache(query, ttl = 1e3) {
266
274
  }
267
275
 
268
276
  // src/core/iframe-bridge.ts
269
- function createIframeBridge(store) {
277
+ var EDITOR_BASE_DOMAIN = "prepr.io";
278
+ function isAllowedEditorOrigin(origin, allowed) {
279
+ if (allowed) return allowed.includes(origin);
280
+ let url;
281
+ try {
282
+ url = new URL(origin);
283
+ } catch {
284
+ return false;
285
+ }
286
+ if (url.protocol !== "https:" || url.port !== "") return false;
287
+ const suffix = `.${EDITOR_BASE_DOMAIN}`;
288
+ if (!url.hostname.endsWith(suffix)) return false;
289
+ const label = url.hostname.slice(0, -suffix.length);
290
+ return label.length > 0 && !label.includes(".");
291
+ }
292
+ function createIframeBridge(store, options = {}) {
293
+ const allowedOrigins = options.allowedEditorOrigins;
270
294
  let parentOrigin = null;
271
295
  const onKeyDown = (event) => {
272
296
  const key = event.key.toLowerCase();
@@ -276,14 +300,16 @@ function createIframeBridge(store) {
276
300
  const onMessage = (evt) => {
277
301
  const data = evt?.data;
278
302
  if (data?.event === "prepr:initVE" && !parentOrigin) {
303
+ if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) return;
279
304
  parentOrigin = evt.origin;
305
+ setTrustedParentOrigin(parentOrigin);
280
306
  if (data.scrollPosition != null) {
281
307
  const top = data.scrollPosition;
282
308
  setTimeout(() => window.scrollTo(0, top), 1);
283
309
  }
284
310
  store.set({ previewMode: true, editMode: data.editMode ?? true });
285
311
  }
286
- if (evt.origin !== parentOrigin) return;
312
+ if (!parentOrigin || evt.origin !== parentOrigin) return;
287
313
  if (data?.event === "prepr:getScrollPosition") {
288
314
  const currentScrollY = window.scrollY || document.documentElement.scrollTop;
289
315
  sendPreprEvent("getScrollPosition", { value: currentScrollY });
@@ -291,13 +317,15 @@ function createIframeBridge(store) {
291
317
  };
292
318
  return {
293
319
  start() {
294
- sendPreprEvent("loaded");
320
+ sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
295
321
  window.addEventListener("keydown", onKeyDown);
296
322
  window.addEventListener("message", onMessage);
297
323
  },
298
324
  stop() {
299
325
  window.removeEventListener("keydown", onKeyDown);
300
326
  window.removeEventListener("message", onMessage);
327
+ parentOrigin = null;
328
+ setTrustedParentOrigin(null);
301
329
  }
302
330
  };
303
331
  }
@@ -937,25 +965,28 @@ function cookieOpts() {
937
965
  }
938
966
  function createChangeHandler(deps) {
939
967
  const { store, stega, syncAutoClean } = deps;
940
- function updateParam(name, value) {
968
+ function updateParams(patch) {
941
969
  const [path, existing] = splitPath(deps.currentPath());
942
970
  const params = new URLSearchParams(existing);
943
- if (value === null) {
944
- params.delete(name);
945
- } else {
946
- params.set(name, value);
971
+ for (const [name, value] of Object.entries(patch)) {
972
+ if (value === null) {
973
+ params.delete(name);
974
+ } else {
975
+ params.set(name, value);
976
+ }
947
977
  }
948
978
  const query = params.toString();
949
979
  deps.navigate(query ? `${path}?${query}` : path);
950
980
  }
951
981
  return function handleChange(before, after) {
982
+ const paramPatch = {};
952
983
  if (before.selectedSegment !== after.selectedSegment) {
953
984
  if (after.selectedSegment === null) {
954
985
  removeCookie(COOKIE_SEGMENT, "/", crossSiteCookieOptions());
955
986
  } else {
956
987
  setCookie(COOKIE_SEGMENT, after.selectedSegment, cookieOpts());
957
988
  }
958
- updateParam(PARAM_SEGMENT, after.selectedSegment);
989
+ paramPatch[PARAM_SEGMENT] = after.selectedSegment;
959
990
  sendPreprEvent("segment_changed", {
960
991
  segment: after.selectedSegment ?? void 0
961
992
  });
@@ -966,11 +997,14 @@ function createChangeHandler(deps) {
966
997
  } else {
967
998
  setCookie(COOKIE_VARIANT, after.selectedVariant, cookieOpts());
968
999
  }
969
- updateParam(PARAM_VARIANT, after.selectedVariant);
1000
+ paramPatch[PARAM_VARIANT] = after.selectedVariant;
970
1001
  sendPreprEvent("variant_changed", {
971
1002
  variant: after.selectedVariant ?? void 0
972
1003
  });
973
1004
  }
1005
+ if (Object.keys(paramPatch).length > 0) {
1006
+ updateParams(paramPatch);
1007
+ }
974
1008
  if (before.editMode !== after.editMode) {
975
1009
  if (after.editMode) {
976
1010
  stega.start();
@@ -2076,7 +2110,9 @@ function createPreprToolbar(opts) {
2076
2110
  });
2077
2111
  syncAutoClean(store.get().previewMode);
2078
2112
  const bridge = createIframeBridge(store);
2079
- sendPreprEvent("getScrollPosition", { value: 0 });
2113
+ sendPreprEvent("getScrollPosition", { value: 0 }, {
2114
+ allowUntrustedTarget: true
2115
+ });
2080
2116
  if (isIframe) {
2081
2117
  bridge.start();
2082
2118
  }
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createPreprToolbar,
4
4
  loadTrackingPixel
5
- } from "../chunk-3FOY6KL4.js";
5
+ } from "../chunk-NLQIRN5O.js";
6
6
 
7
7
  // src/nextjs/components.ts
8
8
  import { usePathname, useRouter } from "next/navigation";
@@ -23,100 +23,23 @@ __export(image_loader_exports, {
23
23
  default: () => preprImageLoader
24
24
  });
25
25
  module.exports = __toCommonJS(image_loader_exports);
26
- var TRANSFORMABLE_HOST_SUFFIXES = [".stream.prepr.io", ".b-cdn.net"];
27
- var KNOWN_OPTION_KEYS = /* @__PURE__ */ new Set([
28
- "w",
29
- // width
30
- "h",
31
- // height
32
- "q",
33
- // quality
34
- "c",
35
- // crop position (north, centre, ...)
36
- "ex",
37
- // crop extract: x offset
38
- "ey",
39
- // crop extract: y offset
40
- "ew",
41
- // crop extract: width
42
- "eh"
43
- // crop extract: height
44
- ]);
45
- function isTransformSegment(segment) {
46
- if (!segment) return false;
47
- const parts = segment.split(",");
48
- let sawKnownOption = false;
49
- for (const part of parts) {
50
- const underscore = part.indexOf("_");
51
- if (underscore <= 0) return false;
52
- const key = part.slice(0, underscore);
53
- const value = part.slice(underscore + 1);
54
- if (!value) return false;
55
- if (KNOWN_OPTION_KEYS.has(key)) {
56
- sawKnownOption = true;
26
+ var TRANSFORM_SEGMENT = /\/([^/]*\bw_\d+[^/]*)\//;
27
+ var isPositiveInteger = (value) => /^\d+$/.test(value);
28
+ function preprImageLoader({ src, width }) {
29
+ return src.replace(TRANSFORM_SEGMENT, (match, segment) => {
30
+ const options = segment.split(",").map((pair) => {
31
+ const underscore = pair.indexOf("_");
32
+ return [pair.slice(0, underscore), pair.slice(underscore + 1)];
33
+ });
34
+ const emittedWidth = options.find(([key]) => key === "w");
35
+ if (!emittedWidth || !isPositiveInteger(emittedWidth[1])) return match;
36
+ const emittedHeight = options.find(([key]) => key === "h");
37
+ if (emittedHeight && isPositiveInteger(emittedHeight[1])) {
38
+ emittedHeight[1] = String(
39
+ Math.round(width * Number(emittedHeight[1]) / Number(emittedWidth[1]))
40
+ );
57
41
  }
58
- }
59
- return sawKnownOption;
60
- }
61
- function parseTransform(segment) {
62
- return segment.split(",").map((part) => {
63
- const underscore = part.indexOf("_");
64
- return [part.slice(0, underscore), part.slice(underscore + 1)];
42
+ emittedWidth[1] = String(width);
43
+ return `/${options.map(([key, value]) => `${key}_${value}`).join(",")}/`;
65
44
  });
66
45
  }
67
- function parsePreprUrl(rawUrl) {
68
- let url;
69
- try {
70
- url = new URL(rawUrl);
71
- } catch {
72
- return null;
73
- }
74
- const isTransformableHost = TRANSFORMABLE_HOST_SUFFIXES.some(
75
- (suffix) => url.hostname.endsWith(suffix)
76
- );
77
- if (!isTransformableHost) return null;
78
- const rawSegments = url.pathname.split("/");
79
- rawSegments.shift();
80
- if (rawSegments.length === 0) return null;
81
- const filename = rawSegments[rawSegments.length - 1];
82
- const leading = rawSegments.slice(0, -1);
83
- let transform = [];
84
- const prefixSegments = [];
85
- for (const segment of leading) {
86
- if (transform.length === 0 && isTransformSegment(segment)) {
87
- transform = parseTransform(segment);
88
- } else if (segment === "") {
89
- continue;
90
- } else {
91
- prefixSegments.push(segment);
92
- }
93
- }
94
- const prefix = url.origin + "/" + (prefixSegments.length > 0 ? prefixSegments.join("/") + "/" : "");
95
- return { prefix, transform, filename: filename + url.search };
96
- }
97
- function buildTransformSegment(pairs) {
98
- return pairs.map(([key, value]) => `${key}_${value}`).join(",");
99
- }
100
- function preprImageLoader({ src, width, quality }) {
101
- const parsed = parsePreprUrl(src);
102
- if (!parsed) return src;
103
- const { prefix, transform, filename } = parsed;
104
- const hasCropExtract = transform.some(([key]) => ["ex", "ey", "ew", "eh"].includes(key));
105
- const next = [];
106
- let setWidth = false;
107
- for (const [key, value] of transform) {
108
- if (key === "w") {
109
- next.push(["w", String(width)]);
110
- setWidth = true;
111
- } else if (key === "h" && !hasCropExtract) {
112
- continue;
113
- } else if (key === "q") {
114
- continue;
115
- } else {
116
- next.push([key, value]);
117
- }
118
- }
119
- if (!setWidth) next.push(["w", String(width)]);
120
- if (quality) next.push(["q", String(quality)]);
121
- return `${prefix}${buildTransformSegment(next)}/${filename}`;
122
- }
@@ -1,10 +1,14 @@
1
1
  import { ImageLoaderProps } from 'next/image';
2
2
 
3
3
  /**
4
- * Rewrites Prepr stream URLs to the width `next/image` asks for, preserving any
5
- * crop/preset options already on the URL. Non-Prepr URLs (other hosts, relative
6
- * paths) come back unchanged — set `unoptimized` on those `<Image>`s.
4
+ * Rewrites Prepr stream URLs to the width `next/image` asks for. URLs with no
5
+ * transform segment (other hosts, relative paths) come back unchanged — set
6
+ * `unoptimized` on those `<Image>`s.
7
+ *
8
+ * No upper clamp: the emitted width is the asset's own size or its crop box,
9
+ * neither of which is a ceiling on what the CDN can serve, so capping there
10
+ * would rule out retina.
7
11
  */
8
- declare function preprImageLoader({ src, width, quality }: ImageLoaderProps): string;
12
+ declare function preprImageLoader({ src, width }: ImageLoaderProps): string;
9
13
 
10
14
  export { preprImageLoader as default };
@@ -1,10 +1,14 @@
1
1
  import { ImageLoaderProps } from 'next/image';
2
2
 
3
3
  /**
4
- * Rewrites Prepr stream URLs to the width `next/image` asks for, preserving any
5
- * crop/preset options already on the URL. Non-Prepr URLs (other hosts, relative
6
- * paths) come back unchanged — set `unoptimized` on those `<Image>`s.
4
+ * Rewrites Prepr stream URLs to the width `next/image` asks for. URLs with no
5
+ * transform segment (other hosts, relative paths) come back unchanged — set
6
+ * `unoptimized` on those `<Image>`s.
7
+ *
8
+ * No upper clamp: the emitted width is the asset's own size or its crop box,
9
+ * neither of which is a ceiling on what the CDN can serve, so capping there
10
+ * would rule out retina.
7
11
  */
8
- declare function preprImageLoader({ src, width, quality }: ImageLoaderProps): string;
12
+ declare function preprImageLoader({ src, width }: ImageLoaderProps): string;
9
13
 
10
14
  export { preprImageLoader as default };
@@ -1,101 +1,24 @@
1
1
  // src/nextjs/image-loader.ts
2
- var TRANSFORMABLE_HOST_SUFFIXES = [".stream.prepr.io", ".b-cdn.net"];
3
- var KNOWN_OPTION_KEYS = /* @__PURE__ */ new Set([
4
- "w",
5
- // width
6
- "h",
7
- // height
8
- "q",
9
- // quality
10
- "c",
11
- // crop position (north, centre, ...)
12
- "ex",
13
- // crop extract: x offset
14
- "ey",
15
- // crop extract: y offset
16
- "ew",
17
- // crop extract: width
18
- "eh"
19
- // crop extract: height
20
- ]);
21
- function isTransformSegment(segment) {
22
- if (!segment) return false;
23
- const parts = segment.split(",");
24
- let sawKnownOption = false;
25
- for (const part of parts) {
26
- const underscore = part.indexOf("_");
27
- if (underscore <= 0) return false;
28
- const key = part.slice(0, underscore);
29
- const value = part.slice(underscore + 1);
30
- if (!value) return false;
31
- if (KNOWN_OPTION_KEYS.has(key)) {
32
- sawKnownOption = true;
2
+ var TRANSFORM_SEGMENT = /\/([^/]*\bw_\d+[^/]*)\//;
3
+ var isPositiveInteger = (value) => /^\d+$/.test(value);
4
+ function preprImageLoader({ src, width }) {
5
+ return src.replace(TRANSFORM_SEGMENT, (match, segment) => {
6
+ const options = segment.split(",").map((pair) => {
7
+ const underscore = pair.indexOf("_");
8
+ return [pair.slice(0, underscore), pair.slice(underscore + 1)];
9
+ });
10
+ const emittedWidth = options.find(([key]) => key === "w");
11
+ if (!emittedWidth || !isPositiveInteger(emittedWidth[1])) return match;
12
+ const emittedHeight = options.find(([key]) => key === "h");
13
+ if (emittedHeight && isPositiveInteger(emittedHeight[1])) {
14
+ emittedHeight[1] = String(
15
+ Math.round(width * Number(emittedHeight[1]) / Number(emittedWidth[1]))
16
+ );
33
17
  }
34
- }
35
- return sawKnownOption;
36
- }
37
- function parseTransform(segment) {
38
- return segment.split(",").map((part) => {
39
- const underscore = part.indexOf("_");
40
- return [part.slice(0, underscore), part.slice(underscore + 1)];
18
+ emittedWidth[1] = String(width);
19
+ return `/${options.map(([key, value]) => `${key}_${value}`).join(",")}/`;
41
20
  });
42
21
  }
43
- function parsePreprUrl(rawUrl) {
44
- let url;
45
- try {
46
- url = new URL(rawUrl);
47
- } catch {
48
- return null;
49
- }
50
- const isTransformableHost = TRANSFORMABLE_HOST_SUFFIXES.some(
51
- (suffix) => url.hostname.endsWith(suffix)
52
- );
53
- if (!isTransformableHost) return null;
54
- const rawSegments = url.pathname.split("/");
55
- rawSegments.shift();
56
- if (rawSegments.length === 0) return null;
57
- const filename = rawSegments[rawSegments.length - 1];
58
- const leading = rawSegments.slice(0, -1);
59
- let transform = [];
60
- const prefixSegments = [];
61
- for (const segment of leading) {
62
- if (transform.length === 0 && isTransformSegment(segment)) {
63
- transform = parseTransform(segment);
64
- } else if (segment === "") {
65
- continue;
66
- } else {
67
- prefixSegments.push(segment);
68
- }
69
- }
70
- const prefix = url.origin + "/" + (prefixSegments.length > 0 ? prefixSegments.join("/") + "/" : "");
71
- return { prefix, transform, filename: filename + url.search };
72
- }
73
- function buildTransformSegment(pairs) {
74
- return pairs.map(([key, value]) => `${key}_${value}`).join(",");
75
- }
76
- function preprImageLoader({ src, width, quality }) {
77
- const parsed = parsePreprUrl(src);
78
- if (!parsed) return src;
79
- const { prefix, transform, filename } = parsed;
80
- const hasCropExtract = transform.some(([key]) => ["ex", "ey", "ew", "eh"].includes(key));
81
- const next = [];
82
- let setWidth = false;
83
- for (const [key, value] of transform) {
84
- if (key === "w") {
85
- next.push(["w", String(width)]);
86
- setWidth = true;
87
- } else if (key === "h" && !hasCropExtract) {
88
- continue;
89
- } else if (key === "q") {
90
- continue;
91
- } else {
92
- next.push([key, value]);
93
- }
94
- }
95
- if (!setWidth) next.push(["w", String(width)]);
96
- if (quality) next.push(["q", String(quality)]);
97
- return `${prefix}${buildTransformSegment(next)}/${filename}`;
98
- }
99
22
  export {
100
23
  preprImageLoader as default
101
24
  };
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(nextjs_exports);
38
38
  var import_server = require("next/server");
39
39
 
40
40
  // src/version.ts
41
- var VERSION = "0.1.0-beta.4";
41
+ var VERSION = "0.1.0-beta.6";
42
42
 
43
43
  // src/core/constants.ts
44
44
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -2,7 +2,7 @@
2
2
  import { NextResponse } from "next/server";
3
3
 
4
4
  // src/version.ts
5
- var VERSION = "0.1.0-beta.4";
5
+ var VERSION = "0.1.0-beta.6";
6
6
 
7
7
  // src/core/constants.ts
8
8
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -35,7 +35,7 @@ __export(nuxt_exports, {
35
35
  module.exports = __toCommonJS(nuxt_exports);
36
36
 
37
37
  // src/version.ts
38
- var VERSION = "0.1.0-beta.4";
38
+ var VERSION = "0.1.0-beta.6";
39
39
 
40
40
  // src/core/constants.ts
41
41
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.1.0-beta.4";
2
+ var VERSION = "0.1.0-beta.6";
3
3
 
4
4
  // src/core/constants.ts
5
5
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -35,7 +35,7 @@ __export(sveltekit_exports, {
35
35
  module.exports = __toCommonJS(sveltekit_exports);
36
36
 
37
37
  // src/version.ts
38
- var VERSION = "0.1.0-beta.4";
38
+ var VERSION = "0.1.0-beta.6";
39
39
 
40
40
  // src/core/constants.ts
41
41
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.1.0-beta.4";
2
+ var VERSION = "0.1.0-beta.6";
3
3
 
4
4
  // src/core/constants.ts
5
5
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
package/package.json CHANGED
@@ -1,8 +1,28 @@
1
1
  {
2
2
  "name": "@preprio/toolkit",
3
- "version": "0.1.0-beta.4",
3
+ "version": "0.1.0-beta.6",
4
4
  "description": "Prepr personalization, A/B testing and preview toolkit for Next.js, Nuxt, Astro, SvelteKit and Express",
5
- "license": "MIT",
5
+ "license": "Apache-2.0",
6
+ "author": "Prepr B.V. (https://prepr.io)",
7
+ "keywords": [
8
+ "prepr",
9
+ "cms",
10
+ "headless-cms",
11
+ "personalization",
12
+ "ab-testing",
13
+ "preview",
14
+ "visual-editing",
15
+ "nextjs",
16
+ "nuxt",
17
+ "astro",
18
+ "sveltekit"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "engines": {
24
+ "node": ">=18.17"
25
+ },
6
26
  "repository": {
7
27
  "type": "git",
8
28
  "url": "git+https://github.com/preprio/prepr-toolkit.git",
@@ -23,6 +43,16 @@
23
43
  "import": "./dist/nextjs/index.js",
24
44
  "require": "./dist/nextjs/index.cjs"
25
45
  },
46
+ "./json-script": {
47
+ "types": "./dist/json-script.d.ts",
48
+ "import": "./dist/json-script.js",
49
+ "require": "./dist/json-script.cjs"
50
+ },
51
+ "./nextjs/components": {
52
+ "types": "./dist/nextjs/components.d.ts",
53
+ "import": "./dist/nextjs/components.js",
54
+ "require": "./dist/nextjs/components.cjs"
55
+ },
26
56
  "./nextjs/image-loader": {
27
57
  "types": "./dist/nextjs/image-loader.d.ts",
28
58
  "import": "./dist/nextjs/image-loader.js",
@@ -53,6 +83,16 @@
53
83
  "src/sveltekit/*.svelte",
54
84
  "src/nuxt/*.vue"
55
85
  ],
86
+ "scripts": {
87
+ "gen:css": "node scripts/compile-css.mjs",
88
+ "prebuild": "rm -rf dist && pnpm gen:css && node scripts/check-version.mjs",
89
+ "pretest": "pnpm gen:css",
90
+ "pretypecheck": "pnpm gen:css",
91
+ "build": "tsup",
92
+ "postbuild": "node scripts/check-exports.mjs",
93
+ "test": "vitest run",
94
+ "typecheck": "tsc --noEmit"
95
+ },
56
96
  "dependencies": {
57
97
  "@vercel/stega": "^1.1.0",
58
98
  "preact": "^10.29.7"
@@ -60,7 +100,9 @@
60
100
  "peerDependencies": {
61
101
  "next": ">=13",
62
102
  "react": ">=17",
63
- "react-dom": ">=17"
103
+ "react-dom": ">=17",
104
+ "svelte": ">=4",
105
+ "vue": ">=3"
64
106
  },
65
107
  "peerDependenciesMeta": {
66
108
  "next": {
@@ -71,6 +113,12 @@
71
113
  },
72
114
  "react-dom": {
73
115
  "optional": true
116
+ },
117
+ "svelte": {
118
+ "optional": true
119
+ },
120
+ "vue": {
121
+ "optional": true
74
122
  }
75
123
  },
76
124
  "devDependencies": {
@@ -86,15 +134,5 @@
86
134
  "tsup": "^8.5.1",
87
135
  "typescript": "^5.9.3",
88
136
  "vitest": "^4.1.10"
89
- },
90
- "scripts": {
91
- "gen:css": "node scripts/compile-css.mjs",
92
- "prebuild": "rm -rf dist && pnpm gen:css && node scripts/check-version.mjs",
93
- "pretest": "pnpm gen:css",
94
- "pretypecheck": "pnpm gen:css",
95
- "build": "tsup",
96
- "postbuild": "node scripts/check-exports.mjs",
97
- "test": "vitest run",
98
- "typecheck": "tsc --noEmit"
99
137
  }
100
- }
138
+ }
@@ -29,6 +29,7 @@
29
29
  * data shape of primitives/arrays.)
30
30
  */
31
31
  import type { PreprToolbarProps } from '@preprio/toolkit';
32
+ import { serializeForScriptTag } from '@preprio/toolkit/json-script';
32
33
 
33
34
  type Props = PreprToolbarProps;
34
35
 
@@ -40,7 +41,7 @@ const props = { activeSegment, activeVariant, segments: segments ?? data ?? [] }
40
41
  <script
41
42
  type="application/json"
42
43
  data-prepr-toolbar-props
43
- set:html={JSON.stringify(props)}
44
+ set:html={serializeForScriptTag(props)}
44
45
  />
45
46
 
46
47
  <script>
@@ -14,6 +14,8 @@
14
14
  * from '@preprio/toolkit'` would fail to resolve in the browser. See
15
15
  * `PreprToolbar.astro` for the full explanation.
16
16
  */
17
+ import { serializeForScriptTag } from '@preprio/toolkit/json-script';
18
+
17
19
  interface Props {
18
20
  id: string;
19
21
  config?: {
@@ -34,7 +36,7 @@ const payload = { id, config };
34
36
  <script
35
37
  type="application/json"
36
38
  data-prepr-pixel-props
37
- set:html={JSON.stringify(payload)}
39
+ set:html={serializeForScriptTag(payload)}
38
40
  />
39
41
 
40
42
  <script>