@preprio/toolkit 0.5.0-rc.2 → 0.5.0-rc.4

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.
Files changed (50) hide show
  1. package/README.md +209 -10
  2. package/dist/astro/index.cjs +1194 -1
  3. package/dist/astro/index.d.cts +47 -1
  4. package/dist/astro/index.d.ts +47 -1
  5. package/dist/astro/index.js +1197 -1
  6. package/dist/chunk-46ISDRHT.js +26 -0
  7. package/dist/chunk-74KY5EAM.js +1219 -0
  8. package/dist/chunk-AHBN6FQP.js +184 -0
  9. package/dist/{chunk-FLPNN3CV.js → chunk-PDNCNTDB.js} +1370 -623
  10. package/dist/chunk-S3VT4UWM.js +75 -0
  11. package/dist/chunk-TG6N3C22.js +246 -0
  12. package/dist/chunk-URXSDJOU.js +356 -0
  13. package/dist/index.cjs +3166 -392
  14. package/dist/index.d.cts +238 -4
  15. package/dist/index.d.ts +238 -4
  16. package/dist/index.js +189 -149
  17. package/dist/live-preview-H4643QYF.js +18 -0
  18. package/dist/nextjs/components.cjs +1522 -359
  19. package/dist/nextjs/components.d.cts +1 -1
  20. package/dist/nextjs/components.d.ts +1 -1
  21. package/dist/nextjs/components.js +4 -1
  22. package/dist/nextjs/index.cjs +1085 -6
  23. package/dist/nextjs/index.d.cts +73 -1
  24. package/dist/nextjs/index.d.ts +73 -1
  25. package/dist/nextjs/index.js +1079 -7
  26. package/dist/nextjs/live-action.cjs +285 -0
  27. package/dist/nextjs/live-action.d.cts +12 -0
  28. package/dist/nextjs/live-action.d.ts +12 -0
  29. package/dist/nextjs/live-action.js +68 -0
  30. package/dist/nextjs/live.cjs +1896 -0
  31. package/dist/nextjs/live.d.cts +52 -0
  32. package/dist/nextjs/live.d.ts +52 -0
  33. package/dist/nextjs/live.js +265 -0
  34. package/dist/nuxt/index.cjs +1 -1
  35. package/dist/nuxt/index.js +1 -1
  36. package/dist/nuxt/live-preview-O2ZTJNW4.js +1520 -0
  37. package/dist/nuxt/live.cjs +1720 -0
  38. package/dist/nuxt/live.d.cts +43 -0
  39. package/dist/nuxt/live.d.ts +43 -0
  40. package/dist/nuxt/live.js +94 -0
  41. package/dist/{pixel-B3oQNbZ3.d.cts → pixel-CX422dCO.d.cts} +10 -1
  42. package/dist/{pixel-B3oQNbZ3.d.ts → pixel-CX422dCO.d.ts} +10 -1
  43. package/dist/react/components.cjs +1522 -359
  44. package/dist/react/components.d.cts +9 -0
  45. package/dist/react/components.d.ts +9 -0
  46. package/dist/react/components.js +1522 -359
  47. package/dist/sveltekit/index.cjs +1 -1
  48. package/dist/sveltekit/index.js +1 -1
  49. package/package.json +12 -1
  50. package/src/astro/PreprToolbar.astro +7 -1
@@ -0,0 +1,52 @@
1
+ /** Executes a registered query. `query` is opaque — string or DocumentNode. */
2
+ interface LiveFetcher {
3
+ (query: unknown, variables?: Record<string, unknown>): Promise<unknown>;
4
+ }
5
+
6
+ interface LivePreviewConfig {
7
+ /**
8
+ * Pass your preview flag; never `true` on a production deployment.
9
+ * Defaults to false: nothing starts and `useLive` is a passthrough.
10
+ */
11
+ enabled?: boolean;
12
+ /** Runs registered queries against Prepr GraphQL in preview mode. */
13
+ fetcher?: LiveFetcher;
14
+ /** Exact editor origins, replacing the `*.prepr.io` default. */
15
+ allowedEditorOrigins?: string[];
16
+ /** Your preview token uses GraphQL strict mode; see the toolbar option. */
17
+ strictMode?: boolean;
18
+ }
19
+ interface PreprRefreshProps {
20
+ /** Pass your preview flag; never `true` on a production deployment. */
21
+ enabled?: boolean;
22
+ /** Your preview token uses GraphQL strict mode; see the toolbar option. */
23
+ strictMode?: boolean;
24
+ }
25
+ interface UseLiveOptions {
26
+ /** Query (string or DocumentNode) passed as-is to the fetcher. */
27
+ query?: unknown;
28
+ variables?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Turn live preview on for `useLive`. Call once on the client with your
32
+ * preview flag as `enabled`. Without a fetcher, saves do not refetch. Not
33
+ * needed when `PreprRefresh` is enabled.
34
+ */
35
+ declare function initLivePreview(config?: LivePreviewConfig): void;
36
+ /**
37
+ * Register a query result for live preview. Field changes re-render
38
+ * instantly; saves refetch `{ query, variables }` when given. A passthrough
39
+ * until live preview is enabled and outside the editor iframe. The data must
40
+ * select `_id` on every level that should update.
41
+ */
42
+ declare function useLive<T>(data: T, opts?: UseLiveOptions): T;
43
+ /**
44
+ * Keeps server components in step with the Prepr editor: unsaved changes
45
+ * re-render the route, where `normalizeQuery` applies them; a save refreshes
46
+ * it. Mount once in the root layout with your preview flag as `enabled`.
47
+ * Use this or `useLive` with a `query`, not both: a save would refetch and
48
+ * refresh.
49
+ */
50
+ declare function PreprRefresh({ enabled, strictMode: strict, }: PreprRefreshProps): null;
51
+
52
+ export { type LivePreviewConfig, PreprRefresh, type PreprRefreshProps, type UseLiveOptions, initLivePreview, useLive };
@@ -0,0 +1,52 @@
1
+ /** Executes a registered query. `query` is opaque — string or DocumentNode. */
2
+ interface LiveFetcher {
3
+ (query: unknown, variables?: Record<string, unknown>): Promise<unknown>;
4
+ }
5
+
6
+ interface LivePreviewConfig {
7
+ /**
8
+ * Pass your preview flag; never `true` on a production deployment.
9
+ * Defaults to false: nothing starts and `useLive` is a passthrough.
10
+ */
11
+ enabled?: boolean;
12
+ /** Runs registered queries against Prepr GraphQL in preview mode. */
13
+ fetcher?: LiveFetcher;
14
+ /** Exact editor origins, replacing the `*.prepr.io` default. */
15
+ allowedEditorOrigins?: string[];
16
+ /** Your preview token uses GraphQL strict mode; see the toolbar option. */
17
+ strictMode?: boolean;
18
+ }
19
+ interface PreprRefreshProps {
20
+ /** Pass your preview flag; never `true` on a production deployment. */
21
+ enabled?: boolean;
22
+ /** Your preview token uses GraphQL strict mode; see the toolbar option. */
23
+ strictMode?: boolean;
24
+ }
25
+ interface UseLiveOptions {
26
+ /** Query (string or DocumentNode) passed as-is to the fetcher. */
27
+ query?: unknown;
28
+ variables?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Turn live preview on for `useLive`. Call once on the client with your
32
+ * preview flag as `enabled`. Without a fetcher, saves do not refetch. Not
33
+ * needed when `PreprRefresh` is enabled.
34
+ */
35
+ declare function initLivePreview(config?: LivePreviewConfig): void;
36
+ /**
37
+ * Register a query result for live preview. Field changes re-render
38
+ * instantly; saves refetch `{ query, variables }` when given. A passthrough
39
+ * until live preview is enabled and outside the editor iframe. The data must
40
+ * select `_id` on every level that should update.
41
+ */
42
+ declare function useLive<T>(data: T, opts?: UseLiveOptions): T;
43
+ /**
44
+ * Keeps server components in step with the Prepr editor: unsaved changes
45
+ * re-render the route, where `normalizeQuery` applies them; a save refreshes
46
+ * it. Mount once in the root layout with your preview flag as `enabled`.
47
+ * Use this or `useLive` with a `query`, not both: a save would refetch and
48
+ * refresh.
49
+ */
50
+ declare function PreprRefresh({ enabled, strictMode: strict, }: PreprRefreshProps): null;
51
+
52
+ export { type LivePreviewConfig, PreprRefresh, type PreprRefreshProps, type UseLiveOptions, initLivePreview, useLive };
@@ -0,0 +1,265 @@
1
+ "use client";
2
+ import {
3
+ createScopedLogger
4
+ } from "../chunk-S3VT4UWM.js";
5
+
6
+ // src/nextjs/live.ts
7
+ import { useRouter } from "next/navigation";
8
+ import { useEffect, useState } from "react";
9
+
10
+ // src/core/live-preview/test-reset.ts
11
+ var resets = /* @__PURE__ */ new Set();
12
+ function onTestReset(reset) {
13
+ resets.add(reset);
14
+ }
15
+
16
+ // src/nextjs/live.ts
17
+ import { syncPreprLiveLog } from "./live-action.js";
18
+ var debug = createScopedLogger("live-preview:next");
19
+ var LOG_SYNC_DEBOUNCE_MS = 150;
20
+ var starting = null;
21
+ var store = null;
22
+ var bridge = null;
23
+ var allowedEditorOrigins;
24
+ var strictMode = false;
25
+ var startListeners = /* @__PURE__ */ new Set();
26
+ function isFramed() {
27
+ return typeof window !== "undefined" && window.self !== window.top;
28
+ }
29
+ function startLive() {
30
+ starting ?? (starting = import("../live-preview-H4643QYF.js").then(
31
+ ({ createLiveBridge, createLiveStore }) => {
32
+ const skipped = new Set(quarantined());
33
+ const started = createLiveStore({
34
+ accept: (patch) => !skipped.has(patchKey(patch))
35
+ });
36
+ watchClientRenders(started);
37
+ bridge = createLiveBridge(started, {
38
+ allowedEditorOrigins: () => allowedEditorOrigins,
39
+ strictMode: () => strictMode
40
+ });
41
+ bridge.start();
42
+ store = started;
43
+ startListeners.forEach((listener) => listener(started));
44
+ startListeners.clear();
45
+ return started;
46
+ }
47
+ ));
48
+ return starting;
49
+ }
50
+ function whenLive(listener) {
51
+ if (store) {
52
+ listener(store);
53
+ return () => {
54
+ };
55
+ }
56
+ startListeners.add(listener);
57
+ return () => startListeners.delete(listener);
58
+ }
59
+ function initLivePreview(config = {}) {
60
+ if (!config.enabled || !isFramed()) return;
61
+ if (config.strictMode) strictMode = true;
62
+ if (config.allowedEditorOrigins) {
63
+ allowedEditorOrigins = [...config.allowedEditorOrigins];
64
+ }
65
+ const { fetcher } = config;
66
+ void startLive().then((started) => {
67
+ if (fetcher) started.setFetcher(fetcher);
68
+ });
69
+ }
70
+ function useLive(data, opts) {
71
+ const [live, setLive] = useState(data);
72
+ useEffect(() => {
73
+ if (!isFramed()) return;
74
+ let unregister = null;
75
+ const stopWaiting = whenLive((started) => {
76
+ const registration = started.register(data, opts);
77
+ setLive(registration.getSnapshot());
78
+ const unsubscribe = registration.subscribe(
79
+ () => setLive(registration.getSnapshot())
80
+ );
81
+ unregister = () => {
82
+ unsubscribe();
83
+ registration.unregister();
84
+ };
85
+ });
86
+ return () => {
87
+ stopWaiting();
88
+ unregister?.();
89
+ };
90
+ }, [data]);
91
+ return isFramed() ? live : data;
92
+ }
93
+ function PreprRefresh({
94
+ enabled = false,
95
+ strictMode: strict = false
96
+ }) {
97
+ const router = useRouter();
98
+ useEffect(() => {
99
+ if (!enabled || !isFramed()) return;
100
+ if (strict) strictMode = true;
101
+ const sync = createLogSync();
102
+ let unsubscribe = null;
103
+ let active = true;
104
+ void startLive().then((started) => {
105
+ if (!active) return;
106
+ const offLog = started.onLogChange((log) => {
107
+ if (log.length) sync.schedule(log);
108
+ else sync.reset();
109
+ });
110
+ const offInvalidate = started.onInvalidate(() => router.refresh());
111
+ unsubscribe = () => {
112
+ offLog();
113
+ offInvalidate();
114
+ };
115
+ });
116
+ return () => {
117
+ active = false;
118
+ unsubscribe?.();
119
+ sync.cancel();
120
+ };
121
+ }, [enabled, strict, router]);
122
+ return null;
123
+ }
124
+ var STEGA_CHARS = /[\u200b-\u200d\u2060-\u2064\ufeff]/;
125
+ var RENDER_SETTLE_MS = 1e3;
126
+ var RENDER_POLL_MS = 100;
127
+ function showsPreprContent() {
128
+ return document.querySelector("[data-prepr-encoded]") !== null || // Visible text only: inline scripts carry the route's data, edit links
129
+ // included, even when the route rendered its error page.
130
+ STEGA_CHARS.test(document.body?.innerText ?? "");
131
+ }
132
+ function watchUntilSettled(onBreak) {
133
+ const interval = setInterval(() => {
134
+ if (showsPreprContent()) return;
135
+ clearInterval(interval);
136
+ onBreak();
137
+ }, RENDER_POLL_MS);
138
+ return () => clearInterval(interval);
139
+ }
140
+ var QUARANTINE_KEY = "__prepr_live_quarantine";
141
+ var MAX_QUARANTINED = 50;
142
+ function quarantined() {
143
+ try {
144
+ const stored = JSON.parse(
145
+ sessionStorage.getItem(QUARANTINE_KEY) ?? "[]"
146
+ );
147
+ return Array.isArray(stored) ? stored.filter((key) => typeof key === "string") : [];
148
+ } catch {
149
+ return [];
150
+ }
151
+ }
152
+ function setQuarantined(keys) {
153
+ try {
154
+ if (keys.length) {
155
+ sessionStorage.setItem(
156
+ QUARANTINE_KEY,
157
+ JSON.stringify(keys.slice(-MAX_QUARANTINED))
158
+ );
159
+ } else {
160
+ sessionStorage.removeItem(QUARANTINE_KEY);
161
+ }
162
+ } catch {
163
+ }
164
+ }
165
+ var patchKey = (patch) => JSON.stringify(patch);
166
+ var CLIENT_RENDER_MS = 300;
167
+ var recovering = false;
168
+ function recoverFrom(log, lastGood) {
169
+ if (recovering) return;
170
+ recovering = true;
171
+ debug.warn("a live change broke the page; reloading without it");
172
+ const good = new Set(lastGood.map(patchKey));
173
+ const broken = log.map(patchKey).filter((key) => !good.has(key));
174
+ setQuarantined([...quarantined(), ...broken]);
175
+ window.location.reload();
176
+ }
177
+ function watchClientRenders(watched) {
178
+ let timer = null;
179
+ let lastGood = [];
180
+ watched.onLogChange((log) => {
181
+ if (timer) clearTimeout(timer);
182
+ timer = null;
183
+ if (!log.length) {
184
+ lastGood = [];
185
+ setQuarantined([]);
186
+ return;
187
+ }
188
+ if (!showsPreprContent()) return;
189
+ timer = setTimeout(() => {
190
+ timer = null;
191
+ if (showsPreprContent()) lastGood = log;
192
+ else recoverFrom(log, lastGood);
193
+ }, CLIENT_RENDER_MS);
194
+ });
195
+ }
196
+ function createLogSync() {
197
+ let timer = null;
198
+ let inFlight = false;
199
+ let pending = null;
200
+ let cancelled = false;
201
+ let lastGood = [];
202
+ const send = async (incoming) => {
203
+ const skipped = new Set(quarantined());
204
+ const log = skipped.size ? incoming.filter((patch) => !skipped.has(patchKey(patch))) : incoming;
205
+ if (!log.length) return;
206
+ inFlight = true;
207
+ const stopWatching = showsPreprContent() ? watchUntilSettled(() => recoverFrom(log, lastGood)) : () => {
208
+ };
209
+ let failed = false;
210
+ try {
211
+ await syncPreprLiveLog(log);
212
+ } catch (error) {
213
+ failed = true;
214
+ debug.log("sending unsaved changes failed", error);
215
+ }
216
+ await new Promise((resolve) => setTimeout(resolve, RENDER_SETTLE_MS));
217
+ stopWatching();
218
+ inFlight = false;
219
+ if (recovering) return;
220
+ if (!failed) lastGood = log;
221
+ if (pending && !cancelled) {
222
+ const next = pending;
223
+ pending = null;
224
+ void send(next);
225
+ }
226
+ };
227
+ return {
228
+ schedule(log) {
229
+ cancelled = false;
230
+ if (timer) clearTimeout(timer);
231
+ timer = setTimeout(() => {
232
+ timer = null;
233
+ if (inFlight) pending = log;
234
+ else void send(log);
235
+ }, LOG_SYNC_DEBOUNCE_MS);
236
+ },
237
+ cancel() {
238
+ cancelled = true;
239
+ pending = null;
240
+ if (timer) clearTimeout(timer);
241
+ timer = null;
242
+ },
243
+ /** After a save: the stored content is what renders now. */
244
+ reset() {
245
+ this.cancel();
246
+ lastGood = [];
247
+ setQuarantined([]);
248
+ }
249
+ };
250
+ }
251
+ onTestReset(() => {
252
+ bridge?.stop();
253
+ bridge = null;
254
+ store = null;
255
+ starting = null;
256
+ recovering = false;
257
+ startListeners.clear();
258
+ allowedEditorOrigins = void 0;
259
+ strictMode = false;
260
+ });
261
+ export {
262
+ PreprRefresh,
263
+ initLivePreview,
264
+ useLive
265
+ };
@@ -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.5.0-rc.2";
38
+ var VERSION = "0.5.0-rc.4";
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.5.0-rc.2";
2
+ var VERSION = "0.5.0-rc.4";
3
3
 
4
4
  // src/core/constants.ts
5
5
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";