@valbuild/next 0.102.0 → 0.103.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.
Files changed (35) hide show
  1. package/README.md +50 -1
  2. package/client/dist/valbuild-next-client.cjs.dev.js +25 -2
  3. package/client/dist/valbuild-next-client.cjs.prod.js +25 -2
  4. package/client/dist/valbuild-next-client.esm.js +25 -2
  5. package/dist/ValApp-90c4171d.cjs.js +7 -0
  6. package/dist/{ValApp-bb207e96.cjs.prod.js → ValApp-90c4171d.cjs.prod.js} +8 -6
  7. package/dist/{ValApp-1992d8b3.cjs.dev.js → ValApp-c1c3ef55.cjs.dev.js} +8 -6
  8. package/dist/{ValApp-8912fc8b.esm.js → ValApp-e17eb734.esm.js} +5 -3
  9. package/dist/ValCanvasBridge-96f67a41.cjs.dev.js +395 -0
  10. package/dist/ValCanvasBridge-d8713d9d.esm.js +387 -0
  11. package/dist/ValCanvasBridge-f75509c6.cjs.js +7 -0
  12. package/dist/ValCanvasBridge-f75509c6.cjs.prod.js +395 -0
  13. package/dist/{ValNextProvider-7302b8af.esm.js → ValNextProvider-3209948c.esm.js} +282 -43
  14. package/dist/{ValNextProvider-03a9f8fd.cjs.dev.js → ValNextProvider-3d8ebfec.cjs.dev.js} +297 -58
  15. package/dist/ValNextProvider-3fdf8756.cjs.js +7 -0
  16. package/dist/{ValNextProvider-6103cb74.cjs.prod.js → ValNextProvider-3fdf8756.cjs.prod.js} +297 -58
  17. package/dist/ValOverlayContext-1d019b95.cjs.js +7 -0
  18. package/dist/{ValOverlayContext-f7f45bc7.cjs.prod.js → ValOverlayContext-1d019b95.cjs.prod.js} +8 -2
  19. package/dist/{ValOverlayContext-c6f27a6d.cjs.dev.js → ValOverlayContext-ca3b6ebd.cjs.dev.js} +8 -2
  20. package/dist/{ValOverlayContext-3c37e5a7.esm.js → ValOverlayContext-f63e0fc9.esm.js} +8 -2
  21. package/dist/declarations/src/ValApp.d.ts +1 -1
  22. package/dist/declarations/src/ValImage.d.ts +1 -1
  23. package/dist/declarations/src/ValProvider.d.ts +1 -1
  24. package/dist/declarations/src/external_exempt_from_val_quickjs.d.ts +7 -5
  25. package/dist/fallbackColors-5ead2c26.esm.js +58 -0
  26. package/dist/{cssUtils-9c11102f.cjs.dev.js → fallbackColors-9a2ac53a.cjs.dev.js} +30 -0
  27. package/dist/{cssUtils-d97a757c.cjs.prod.js → fallbackColors-d4a5b0da.cjs.prod.js} +30 -0
  28. package/dist/valbuild-next.cjs.dev.js +8 -13
  29. package/dist/valbuild-next.cjs.prod.js +8 -13
  30. package/dist/valbuild-next.esm.js +9 -10
  31. package/package.json +13 -13
  32. package/dist/ValApp-bb207e96.cjs.js +0 -7
  33. package/dist/ValNextProvider-6103cb74.cjs.js +0 -7
  34. package/dist/ValOverlayContext-f7f45bc7.cjs.js +0 -7
  35. package/dist/cssUtils-b5651c03.esm.js +0 -32
@@ -6,11 +6,12 @@ import { VERSION, VAL_APP_PATH, VAL_OVERLAY_ID } from '@valbuild/ui';
6
6
  import { useRouter, usePathname } from 'next/navigation';
7
7
  import Script from 'next/script';
8
8
  import React, { useEffect } from 'react';
9
- import { ValExternalStore, ValOverlayProvider } from './ValOverlayContext-3c37e5a7.esm.js';
9
+ import { ValExternalStore, ValOverlayProvider } from './ValOverlayContext-f63e0fc9.esm.js';
10
10
  import { SET_AUTO_TAG_JSX_ENABLED } from '@valbuild/react/stega';
11
- import { VAL_THEME_SESSION_STORAGE_KEY, createValClient } from '@valbuild/shared/internal';
12
- import { p as prefixStyles, u as useConfigStorageSave, v as valPrefixedClass, c as cn } from './cssUtils-b5651c03.esm.js';
11
+ import { VAL_THEME_SESSION_STORAGE_KEY, createValClient, isValCanvasFrame } from '@valbuild/shared/internal';
12
+ import { p as prefixStyles, u as useConfigStorageSave, f as floatDarkBg, d as floatLightBg, v as valPrefixedClass, c as cn } from './fallbackColors-5ead2c26.esm.js';
13
13
  import { _ as _createForOfIteratorHelper } from './createForOfIteratorHelper-5758a730.esm.js';
14
+ import { ValCanvasBridge } from './ValCanvasBridge-d8713d9d.esm.js';
14
15
  import { jsxs, jsx } from 'react/jsx-runtime';
15
16
  import './unsupportedIterableToArray-5baabfdc.esm.js';
16
17
  import './defineProperty-cca5affa.esm.js';
@@ -67,7 +68,77 @@ function hasValEnableCookie(cookieString) {
67
68
  return false;
68
69
  }
69
70
 
71
+ /**
72
+ * Whether the page should re-read the server on its own, right now.
73
+ *
74
+ * The page cannot know when its own edit has been persisted. An edit is applied
75
+ * to the client store immediately and written to the server asynchronously, so
76
+ * a `router.refresh()` fired on the edit can reach a server that has not saved
77
+ * the patch yet: the payload comes back with the old content, and nothing
78
+ * schedules another look. What an editor sees is a canvas that flickered and did
79
+ * not change — which is indistinguishable from an edit that did not work.
80
+ *
81
+ * So the page asks again a little later. This is the "should I bother" half of
82
+ * that, kept out of the effect and pure so the reasons NOT to are checkable:
83
+ * every one of them is a whole-route request avoided, and in development Next
84
+ * re-renders (and sometimes recompiles) the page for each one.
85
+ */
86
+ function shouldSafetyRefresh(state) {
87
+ // Never edited: a preview someone is only looking at has nothing to catch up
88
+ // on, and polling it would cost a route render per tick for nothing.
89
+ if (state.lastEditAt === 0) return false;
90
+ // The editing stopped long enough ago that anything in flight has landed.
91
+ if (state.now - state.lastEditAt > state.windowMs) return false;
92
+ // Nobody is looking. The caller refreshes once on the way back instead.
93
+ if (state.hidden) return false;
94
+ // Already on its way. Stacking whole-route requests makes the slowness this
95
+ // exists to paper over worse, not better.
96
+ if (state.isRefreshing) return false;
97
+ // One just went out — usually the edit-driven refresh, a moment ago.
98
+ if (state.now - state.lastRefreshAt < state.minIntervalMs) return false;
99
+ return true;
100
+ }
101
+
102
+ var REFRESH_INTERVAL_MS = 500;
103
+
104
+ /**
105
+ * How often the page re-reads the server on its own, without being told to.
106
+ *
107
+ * A safety net for a race no amount of care on this side can close. An edit is
108
+ * applied to the client store immediately and saved to the server
109
+ * asynchronously, so `router.refresh()` fired on the edit can reach a server
110
+ * that has not written the patch yet: the RSC payload comes back with the old
111
+ * content, the counter has already been cleared, and nothing schedules another
112
+ * look. What you see is a canvas that flickered and did not change — which is
113
+ * indistinguishable from an edit that did not work.
114
+ *
115
+ * The honest fix would be for the page to know when its patch has been
116
+ * persisted, and it has no way to ask. So it asks again a little later instead.
117
+ */
118
+ var SAFETY_REFRESH_MS = 10000;
119
+
120
+ /**
121
+ * How long after an edit the safety net keeps looking.
122
+ *
123
+ * The reason to stop is that this is a whole-route request: in development Next
124
+ * re-renders (and sometimes recompiles) the page for every one, and a preview
125
+ * nobody is editing has nothing to catch up on. So the net is armed by editing
126
+ * and disarms itself when the editing stops — long enough after the last
127
+ * keystroke that any write still in flight has certainly landed.
128
+ */
129
+ var SAFETY_REFRESH_WINDOW_MS = 30000;
130
+
131
+ /**
132
+ * Fired on this window when an edit has been applied to the client store.
133
+ *
134
+ * The refresh loop listens for it so it can start the round trip immediately
135
+ * rather than on its next tick. A DOM event rather than state, because the
136
+ * counter it accompanies is a ref and the loop lives in an effect that must not
137
+ * be torn down and rebuilt per keystroke.
138
+ */
139
+ var VAL_EDIT_LANDED = "val-edit-landed";
70
140
  var ValNextProvider = function ValNextProvider(props) {
141
+ var _draftModeReady$curre2;
71
142
  // TODO: use config:
72
143
  var route = "/api/val";
73
144
  var client = React.useMemo(function () {
@@ -93,28 +164,102 @@ var ValNextProvider = function ValNextProvider(props) {
93
164
  _React$useState2 = _slicedToArray(_React$useState, 2),
94
165
  suspendActive = _React$useState2[0],
95
166
  setSuspendActive = _React$useState2[1];
96
- var _React$useState3 = React.useState(),
167
+ /**
168
+ * Whether the studio has said it sent everything it holds.
169
+ *
170
+ * Until then, a module missing from the store might still be on its way. After
171
+ * it, a missing module simply has no draft — see `sourcesSynced` in the canvas
172
+ * protocol. Without this the page could only wait out `waitForLoad`'s timeout,
173
+ * ten seconds per module nobody had edited.
174
+ */
175
+ var _React$useState3 = React.useState(false),
97
176
  _React$useState4 = _slicedToArray(_React$useState3, 2),
98
- mountOverlay = _React$useState4[0],
99
- setMountOverlay = _React$useState4[1];
100
- var _React$useState5 = React.useState(null),
177
+ draftSourcesSynced = _React$useState4[0],
178
+ setDraftSourcesSynced = _React$useState4[1];
179
+ var _React$useState5 = React.useState(),
101
180
  _React$useState6 = _slicedToArray(_React$useState5, 2),
102
- draftMode = _React$useState6[0],
103
- setDraftMode = _React$useState6[1];
181
+ mountOverlay = _React$useState6[0],
182
+ setMountOverlay = _React$useState6[1];
183
+ /**
184
+ * Whether this document is the studio's canvas frame.
185
+ *
186
+ * The canvas needs everything `mountOverlay` turns on — draft content,
187
+ * `data-val-path` on every tracked element, the refresh loop that brings an
188
+ * edit across — and none of what it *shows*, because the studio around the
189
+ * frame is already that UI. So it is a second flag rather than a different
190
+ * value of the first one: two overlays on one screen, one of them inside the
191
+ * other, is the thing to avoid.
192
+ *
193
+ * Read once, in the same effect as `mountOverlay`: it is a property of how
194
+ * the document was loaded, and it cannot change without a navigation.
195
+ */
104
196
  var _React$useState7 = React.useState(false),
105
197
  _React$useState8 = _slicedToArray(_React$useState7, 2),
106
- spaReady = _React$useState8[0],
107
- setSpaReady = _React$useState8[1]; // TODO: consider removing spaReady - it is not used? If we remove, clean up the custom events that send the message too...
198
+ isCanvas = _React$useState8[0],
199
+ setIsCanvas = _React$useState8[1];
200
+ var _React$useState9 = React.useState(null),
201
+ _React$useState0 = _slicedToArray(_React$useState9, 2),
202
+ draftMode = _React$useState0[0],
203
+ setDraftMode = _React$useState0[1];
204
+ /**
205
+ * Resolves when `draftMode` stops being unknown.
206
+ *
207
+ * `null` is the state before `/draft/stat` has answered, and it is NOT a
208
+ * synonym for "off": the reader that turns a selector into content treats it
209
+ * as off, so a render that happens while draft mode is still unknown resolves
210
+ * against committed source. For a route that only exists in an uncommitted
211
+ * patch that means `notFound()` — terminal, before the answer even arrives.
212
+ *
213
+ * So a page that is going to suspend waits for the answer first. A promise
214
+ * rather than a re-render, because the thing that has to wait is a render.
215
+ */
216
+ var draftModeReady = React.useRef(null);
217
+ // Only for a page that opted in. A visitor with Val off never reads it, and
218
+ // an object created per mount for nobody is the kind of cost that is invisible
219
+ // until it is in every page of every app.
220
+ if (props.suspend && draftModeReady.current === null) {
221
+ var resolve = function resolve() {
222
+ return undefined;
223
+ };
224
+ var promise = new Promise(function (r) {
225
+ resolve = r;
226
+ });
227
+ draftModeReady.current = {
228
+ promise: promise,
229
+ resolve: resolve
230
+ };
231
+ }
232
+ React.useEffect(function () {
233
+ if (draftMode !== null) {
234
+ var _draftModeReady$curre;
235
+ (_draftModeReady$curre = draftModeReady.current) === null || _draftModeReady$curre === void 0 || _draftModeReady$curre.resolve();
236
+ }
237
+ }, [draftMode]);
238
+ var _React$useState1 = React.useState(false),
239
+ _React$useState10 = _slicedToArray(_React$useState1, 2),
240
+ spaReady = _React$useState10[0],
241
+ setSpaReady = _React$useState10[1]; // TODO: consider removing spaReady - it is not used? If we remove, clean up the custom events that send the message too...
108
242
  var router = useRouter();
109
243
  var _React$useTransition = React.useTransition(),
110
244
  _React$useTransition2 = _slicedToArray(_React$useTransition, 2),
245
+ isRefreshing = _React$useTransition2[0],
111
246
  startTransition = _React$useTransition2[1];
247
+ /**
248
+ * The same answer as `isRefreshing`, readable from the refresh effect.
249
+ *
250
+ * The effect must not be rebuilt every time a refresh starts and ends — it
251
+ * owns the timers and the "when did we last edit" bookkeeping, and tearing
252
+ * that down mid-flurry loses it. A ref updated on render is the value without
253
+ * the dependency.
254
+ */
255
+ var isRefreshingRef = React.useRef(false);
112
256
  var rerenderCounterRef = React.useRef(0);
113
- var _React$useState9 = React.useState(null),
114
- _React$useState0 = _slicedToArray(_React$useState9, 2),
115
- iframeSrc = _React$useState0[0],
116
- setIframeSrc = _React$useState0[1];
257
+ var _React$useState11 = React.useState(null),
258
+ _React$useState12 = _slicedToArray(_React$useState11, 2),
259
+ iframeSrc = _React$useState12[0],
260
+ setIframeSrc = _React$useState12[1];
117
261
  var pathname = usePathname();
262
+ isRefreshingRef.current = isRefreshing;
118
263
  useEffect(function () {
119
264
  window.dispatchEvent(new CustomEvent("val-provider:pathname", {
120
265
  detail: pathname
@@ -130,6 +275,7 @@ var ValNextProvider = function ValNextProvider(props) {
130
275
  setMountOverlay(false);
131
276
  return;
132
277
  }
278
+ setIsCanvas(isValCanvasFrame(location.search));
133
279
  setMountOverlay(hasValEnableCookie(document.cookie));
134
280
  }, []);
135
281
  React.useEffect(function () {
@@ -145,22 +291,101 @@ var ValNextProvider = function ValNextProvider(props) {
145
291
  });
146
292
  }
147
293
  }, [props.suspend]);
294
+
295
+ /**
296
+ * Re-render the server tree when an edit lands, then at most every 500ms.
297
+ *
298
+ * Leading edge, which is the whole point. This polled every 500ms and only
299
+ * refreshed on the tick, so a keystroke waited up to half a second before the
300
+ * round trip even began — pure dead time on top of a request that is already
301
+ * the slow part in development. Refreshing on arrival and *then* enforcing the
302
+ * gap keeps the same ceiling on requests while removing the wait.
303
+ *
304
+ * Still a counter rather than a boolean: edits that arrive during the gap have
305
+ * to be picked up by the next refresh, and the count is what says whether
306
+ * there are any.
307
+ */
148
308
  React.useEffect(function () {
149
- if (!mountOverlay) {
309
+ if (!mountOverlay || props.disableRefresh) {
150
310
  return;
151
311
  }
152
- var interval = setInterval(function () {
153
- if (rerenderCounterRef.current > 0) {
154
- if (!props.disableRefresh) {
155
- rerenderCounterRef.current = 0;
156
- startTransition(function () {
157
- router.refresh();
158
- });
159
- }
312
+ var timer = null;
313
+ var lastRefreshAt = 0;
314
+ /** When an edit last landed here. 0 means this page has never been edited. */
315
+ var lastEditAt = 0;
316
+ /** Whether a safety tick was skipped because nobody was looking. */
317
+ var missedWhileHidden = false;
318
+ var refresh = function refresh() {
319
+ rerenderCounterRef.current = 0;
320
+ lastRefreshAt = Date.now();
321
+ startTransition(function () {
322
+ router.refresh();
323
+ });
324
+ };
325
+ var _tick = function tick() {
326
+ timer = null;
327
+ if (rerenderCounterRef.current === 0) return;
328
+ var since = Date.now() - lastRefreshAt;
329
+ if (since >= REFRESH_INTERVAL_MS) {
330
+ refresh();
331
+ } else {
332
+ timer = setTimeout(_tick, REFRESH_INTERVAL_MS - since);
160
333
  }
161
- }, 500);
334
+ };
335
+ var onEdit = function onEdit() {
336
+ lastEditAt = Date.now();
337
+ if (timer !== null) return;
338
+ timer = setTimeout(_tick, 0);
339
+ };
340
+ window.addEventListener(VAL_EDIT_LANDED, onEdit);
341
+ // A backstop for the counter being bumped without the event — the two are
342
+ // incremented in the same places today, but the poll is what made this
343
+ // correct regardless of who bumped it.
344
+ var interval = setInterval(_tick, REFRESH_INTERVAL_MS);
345
+
346
+ /**
347
+ * The safety net: look again, whether or not anything told us to.
348
+ *
349
+ * The reasons not to are in `shouldSafetyRefresh`, where they can be tested.
350
+ * `hidden` is the one this side has to remember something about: a skipped
351
+ * tick is worth one look when the tab comes back.
352
+ */
353
+ var safetyTick = function safetyTick() {
354
+ if (document.hidden) {
355
+ missedWhileHidden = true;
356
+ return;
357
+ }
358
+ var go = shouldSafetyRefresh({
359
+ now: Date.now(),
360
+ lastEditAt: lastEditAt,
361
+ lastRefreshAt: lastRefreshAt,
362
+ hidden: false,
363
+ isRefreshing: isRefreshingRef.current,
364
+ windowMs: SAFETY_REFRESH_WINDOW_MS,
365
+ minIntervalMs: REFRESH_INTERVAL_MS
366
+ });
367
+ if (go) refresh();
368
+ };
369
+ var safety = setInterval(safetyTick, SAFETY_REFRESH_MS);
370
+ /**
371
+ * Coming back to the tab is worth one look.
372
+ *
373
+ * Otherwise a background tab skips its ticks and then has to wait out a
374
+ * whole interval on return — which is exactly the moment someone is looking
375
+ * at the page to see whether their edit took.
376
+ */
377
+ var onVisibilityChange = function onVisibilityChange() {
378
+ if (document.hidden || !missedWhileHidden) return;
379
+ missedWhileHidden = false;
380
+ safetyTick();
381
+ };
382
+ document.addEventListener("visibilitychange", onVisibilityChange);
162
383
  return function () {
384
+ window.removeEventListener(VAL_EDIT_LANDED, onEdit);
385
+ document.removeEventListener("visibilitychange", onVisibilityChange);
163
386
  clearInterval(interval);
387
+ clearInterval(safety);
388
+ if (timer !== null) clearTimeout(timer);
164
389
  };
165
390
  }, [mountOverlay, props.disableRefresh]);
166
391
  React.useEffect(function () {
@@ -245,6 +470,7 @@ var ValNextProvider = function ValNextProvider(props) {
245
470
  setDraftMode(function (prev) {
246
471
  if (prev !== res.json.draftMode) {
247
472
  rerenderCounterRef.current++;
473
+ window.dispatchEvent(new Event(VAL_EDIT_LANDED));
248
474
  return res.json.draftMode;
249
475
  }
250
476
  return prev;
@@ -283,15 +509,20 @@ var ValNextProvider = function ValNextProvider(props) {
283
509
  SET_AUTO_TAG_JSX_ENABLED(true);
284
510
  var reactServerComponentRefreshListener = function reactServerComponentRefreshListener(event) {
285
511
  if (event instanceof CustomEvent) {
286
- var _event$detail;
287
- if (((_event$detail = event.detail) === null || _event$detail === void 0 ? void 0 : _event$detail.type) === "source-update") {
288
- var _event$detail2, _event$detail3;
289
- var moduleFilePath = (_event$detail2 = event.detail) === null || _event$detail2 === void 0 ? void 0 : _event$detail2.moduleFilePath;
290
- var source = (_event$detail3 = event.detail) === null || _event$detail3 === void 0 ? void 0 : _event$detail3.source;
512
+ var _event$detail, _event$detail2;
513
+ if (((_event$detail = event.detail) === null || _event$detail === void 0 ? void 0 : _event$detail.type) === "sources-synced") {
514
+ setDraftSourcesSynced(true);
515
+ return;
516
+ }
517
+ if (((_event$detail2 = event.detail) === null || _event$detail2 === void 0 ? void 0 : _event$detail2.type) === "source-update") {
518
+ var _event$detail3, _event$detail4;
519
+ var moduleFilePath = (_event$detail3 = event.detail) === null || _event$detail3 === void 0 ? void 0 : _event$detail3.moduleFilePath;
520
+ var source = (_event$detail4 = event.detail) === null || _event$detail4 === void 0 ? void 0 : _event$detail4.source;
291
521
  if (typeof moduleFilePath === "string" && source !== undefined) {
292
522
  valStore.update(moduleFilePath, source);
293
523
  if (!props.disableRefresh) {
294
524
  rerenderCounterRef.current++;
525
+ window.dispatchEvent(new Event(VAL_EDIT_LANDED));
295
526
  }
296
527
  } else {
297
528
  console.error("Val: invalid event detail", event.detail);
@@ -331,10 +562,10 @@ var ValNextProvider = function ValNextProvider(props) {
331
562
  window.dispatchEvent(new CustomEvent("val-append-overlay"));
332
563
  }
333
564
  });
334
- var _React$useState1 = React.useState(null),
335
- _React$useState10 = _slicedToArray(_React$useState1, 2),
336
- dropZone = _React$useState10[0],
337
- setDropZone = _React$useState10[1];
565
+ var _React$useState13 = React.useState(null),
566
+ _React$useState14 = _slicedToArray(_React$useState13, 2),
567
+ dropZone = _React$useState14[0],
568
+ setDropZone = _React$useState14[1];
338
569
  React.useEffect(function () {
339
570
  var storedDropZone = localStorage.getItem("val-menu-drop-zone-default");
340
571
  if (storedDropZone) {
@@ -347,10 +578,10 @@ var ValNextProvider = function ValNextProvider(props) {
347
578
  var initTheme = React.useMemo(function () {
348
579
  return initSessionTheme(props.config);
349
580
  }, [props.config]);
350
- var _React$useState11 = React.useState(false),
351
- _React$useState12 = _slicedToArray(_React$useState11, 2),
352
- spaLoaded = _React$useState12[0],
353
- setSpaLoaded = _React$useState12[1];
581
+ var _React$useState15 = React.useState(false),
582
+ _React$useState16 = _slicedToArray(_React$useState15, 2),
583
+ spaLoaded = _React$useState16[0],
584
+ setSpaLoaded = _React$useState16[1];
354
585
  React.useEffect(function () {
355
586
  var listener = function listener() {
356
587
  setSpaLoaded(true);
@@ -360,12 +591,13 @@ var ValNextProvider = function ValNextProvider(props) {
360
591
  window.removeEventListener("val-ui-created", listener);
361
592
  };
362
593
  }, []);
594
+ // The pill is Val's chrome floating over the customer's page, so it takes
595
+ // the float background rather than the studio's canvas.
363
596
  var _React$useMemo = React.useMemo(function () {
364
597
  if (initTheme !== "light") {
365
- return ["#0c111d", "white"];
366
- } else {
367
- return ["white", "black"];
598
+ return [floatDarkBg, "white"];
368
599
  }
600
+ return [floatLightBg, "black"];
369
601
  }, [initTheme]),
370
602
  _React$useMemo2 = _slicedToArray(_React$useMemo, 2),
371
603
  backgroundColor = _React$useMemo2[0],
@@ -390,9 +622,13 @@ var ValNextProvider = function ValNextProvider(props) {
390
622
  }, [valPrefixedClass]);
391
623
  return /*#__PURE__*/jsxs(ValOverlayProvider, {
392
624
  draftMode: draftMode,
625
+ draftModeReady: (_draftModeReady$curre2 = draftModeReady.current) === null || _draftModeReady$curre2 === void 0 ? void 0 : _draftModeReady$curre2.promise,
626
+ draftSourcesSynced: draftSourcesSynced,
393
627
  suspend: suspendActive,
394
628
  store: valStore,
395
- children: [props.children, dropZone !== null && !spaLoaded && mountOverlay && initTheme !== null && /*#__PURE__*/jsxs(React.Fragment, {
629
+ children: [props.children, dropZone !== null && !spaLoaded && mountOverlay &&
630
+ // The canvas frame loads no SPA, so its pill would spin for ever.
631
+ !isCanvas && initTheme !== null && /*#__PURE__*/jsxs(React.Fragment, {
396
632
  children: [/*#__PURE__*/jsx("style", {
397
633
  children: "\n".concat(positionStyles, "\n").concat(prefixStyles(commonStyles), "\n@keyframes rotate-clock {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n")
398
634
  }), /*#__PURE__*/jsx("div", {
@@ -435,7 +671,7 @@ var ValNextProvider = function ValNextProvider(props) {
435
671
  })
436
672
  })
437
673
  })]
438
- }), mountOverlay && /*#__PURE__*/jsxs(React.Fragment, {
674
+ }), mountOverlay && !isCanvas && /*#__PURE__*/jsxs(React.Fragment, {
439
675
  children: [/*#__PURE__*/jsx(Script, {
440
676
  type: "module",
441
677
  src: "".concat(route, "/static").concat(VERSION ? "/".concat(VERSION) : "").concat(VAL_APP_PATH),
@@ -444,6 +680,9 @@ var ValNextProvider = function ValNextProvider(props) {
444
680
  id: VAL_OVERLAY_ID,
445
681
  ref: container
446
682
  })]
683
+ }), mountOverlay && isCanvas && /*#__PURE__*/jsx(ValCanvasBridge, {
684
+ draftMode: draftMode === true,
685
+ isRefreshing: isRefreshing
447
686
  }), mountOverlay && iframeSrc && /*#__PURE__*/jsx("iframe", {
448
687
  loading: "eager",
449
688
  style: {