@valbuild/tanstack 0.124.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 (50) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +380 -0
  3. package/client/dist/valbuild-tanstack-client.cjs.d.ts +2 -0
  4. package/client/dist/valbuild-tanstack-client.cjs.dev.js +288 -0
  5. package/client/dist/valbuild-tanstack-client.cjs.js +7 -0
  6. package/client/dist/valbuild-tanstack-client.cjs.prod.js +288 -0
  7. package/client/dist/valbuild-tanstack-client.esm.js +280 -0
  8. package/client/package.json +4 -0
  9. package/dist/ValOverlayContext-87cb022d.esm.js +284 -0
  10. package/dist/ValOverlayContext-abd6ab9a.cjs.prod.js +292 -0
  11. package/dist/ValOverlayContext-d3bfacb1.cjs.dev.js +292 -0
  12. package/dist/createForOfIteratorHelper-0324e063.cjs.prod.js +157 -0
  13. package/dist/createForOfIteratorHelper-485c2ce1.esm.js +151 -0
  14. package/dist/createForOfIteratorHelper-e75681d7.cjs.dev.js +157 -0
  15. package/dist/declarations/src/ValApp.d.ts +6 -0
  16. package/dist/declarations/src/ValImage.d.ts +24 -0
  17. package/dist/declarations/src/ValModulesClient.d.ts +34 -0
  18. package/dist/declarations/src/ValProvider.d.ts +6 -0
  19. package/dist/declarations/src/ValTypes.d.ts +4 -0
  20. package/dist/declarations/src/client/index.d.ts +1 -0
  21. package/dist/declarations/src/client/initValClient.d.ts +27 -0
  22. package/dist/declarations/src/decodeValPathsOfString.d.ts +2 -0
  23. package/dist/declarations/src/external_exempt_from_val_quickjs.d.ts +41 -0
  24. package/dist/declarations/src/getUnpatchedUnencodedVal.d.ts +3 -0
  25. package/dist/declarations/src/index.d.ts +2 -0
  26. package/dist/declarations/src/initVal.d.ts +74 -0
  27. package/dist/declarations/src/server/index.d.ts +5 -0
  28. package/dist/declarations/src/server/initValContent.d.ts +122 -0
  29. package/dist/declarations/src/server/initValMcp.d.ts +27 -0
  30. package/dist/declarations/src/server/initValServer.d.ts +41 -0
  31. package/dist/declarations/src/server/valDraftMode.d.ts +49 -0
  32. package/dist/declarations/src/version.d.ts +1 -0
  33. package/dist/routeFromVal-6693e037.cjs.prod.js +218 -0
  34. package/dist/routeFromVal-728295a0.esm.js +212 -0
  35. package/dist/routeFromVal-a9147ceb.cjs.dev.js +218 -0
  36. package/dist/valbuild-tanstack.cjs.d.ts +2 -0
  37. package/dist/valbuild-tanstack.cjs.dev.js +2005 -0
  38. package/dist/valbuild-tanstack.cjs.js +7 -0
  39. package/dist/valbuild-tanstack.cjs.prod.js +2005 -0
  40. package/dist/valbuild-tanstack.esm.js +1950 -0
  41. package/dist/version-1a88fd21.cjs.dev.js +229 -0
  42. package/dist/version-5ac70ccf.cjs.prod.js +229 -0
  43. package/dist/version-ac8df696.esm.js +225 -0
  44. package/package.json +84 -0
  45. package/server/dist/valbuild-tanstack-server.cjs.d.ts +2 -0
  46. package/server/dist/valbuild-tanstack-server.cjs.dev.js +1032 -0
  47. package/server/dist/valbuild-tanstack-server.cjs.js +7 -0
  48. package/server/dist/valbuild-tanstack-server.cjs.prod.js +1032 -0
  49. package/server/dist/valbuild-tanstack-server.esm.js +1005 -0
  50. package/server/package.json +4 -0
@@ -0,0 +1,1950 @@
1
+ import { _ as _asyncToGenerator, a as _regenerator, V as VERSION$1 } from './version-ac8df696.esm.js';
2
+ import { _ as _objectSpread2, a as _slicedToArray, b as _createForOfIteratorHelper } from './createForOfIteratorHelper-485c2ce1.esm.js';
3
+ import { Internal as Internal$1, initVal as initVal$1, DEFAULT_CONTENT_HOST } from '@valbuild/core';
4
+ import * as core from '@valbuild/core';
5
+ export { core as expr };
6
+ export { GenericSelector, Schema, VAL_EXTENSION, derefPatch, modules } from '@valbuild/core';
7
+ import { stegaEncode, stegaDecodeStrings, attrs, raw, SET_AUTO_TAG_JSX_ENABLED, autoTagJSX } from '@valbuild/react/stega';
8
+ export { ValRichText } from '@valbuild/react/internal';
9
+ import { VERSION, VAL_APP_PATH, VAL_OVERLAY_ID, VAL_APP_ID } from '@valbuild/ui';
10
+ import { useRouter, useRouterState } from '@tanstack/react-router';
11
+ import React, { useEffect, useState, useRef } from 'react';
12
+ import { V as ValExternalStore, a as ValOverlayProvider } from './ValOverlayContext-87cb022d.esm.js';
13
+ import { VAL_CONFIG_SESSION_STORAGE_KEY, VAL_THEME_SESSION_STORAGE_KEY, VAL_CANVAS_MESSAGE, isValCanvasStudioMessage, isValCanvasFrame } from '@valbuild/shared/client';
14
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
+ import '@valbuild/core/patch';
16
+
17
+ function getUnpatchedUnencodedVal(selector) {
18
+ // It is weird to use stega encode here since we never actually should stega encode,
19
+ // however, this is a workaround, since if it is disabled, it will behave as we want
20
+ return stegaEncode(selector, {
21
+ disabled: true
22
+ });
23
+ }
24
+
25
+ function decodeValPathsOfString(encodedString) {
26
+ return stegaDecodeStrings(encodedString);
27
+ }
28
+
29
+ var tanstackRouter = Internal$1.tanstackRouter;
30
+ var externalPageRouter = Internal$1.externalPageRouter;
31
+
32
+ /**
33
+ * Returns true if the Val Enable cookie is set. Must be called on the server —
34
+ * inside a server function, a server route handler, or during SSR — and
35
+ * returns false anywhere else.
36
+ *
37
+ * Not needed for the `suspend` prop on ValProvider, which detects the cookie
38
+ * client-side; reserve it for advanced server-side conditionals.
39
+ */
40
+ function isValEnabled() {
41
+ return _isValEnabled.apply(this, arguments);
42
+ }
43
+ function _isValEnabled() {
44
+ _isValEnabled = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
45
+ var _yield$import, getCookie;
46
+ return _regenerator().w(function (_context) {
47
+ while (1) switch (_context.p = _context.n) {
48
+ case 0:
49
+ _context.p = 0;
50
+ _context.n = 1;
51
+ return import('@tanstack/react-start/server');
52
+ case 1:
53
+ _yield$import = _context.v;
54
+ getCookie = _yield$import.getCookie;
55
+ return _context.a(2, getCookie(Internal$1.VAL_ENABLE_COOKIE_NAME) === "true");
56
+ case 2:
57
+ _context.p = 2;
58
+ _context.v;
59
+ return _context.a(2, false);
60
+ }
61
+ }, _callee, null, [[0, 2]]);
62
+ }));
63
+ return _isValEnabled.apply(this, arguments);
64
+ }
65
+ var initVal = function initVal(config) {
66
+ var _createValSystem = initVal$1(config),
67
+ s = _createValSystem.s,
68
+ c = _createValSystem.c,
69
+ val = _createValSystem.val,
70
+ systemConfig = _createValSystem.config;
71
+ var currentConfig = _objectSpread2(_objectSpread2({}, systemConfig), config);
72
+ return {
73
+ s: s,
74
+ c: c,
75
+ isValEnabled: isValEnabled,
76
+ tanstackRouter: tanstackRouter,
77
+ externalPageRouter: externalPageRouter,
78
+ val: _objectSpread2(_objectSpread2({}, val), {}, {
79
+ attrs: attrs,
80
+ unstable_decodeValPathsOfString: decodeValPathsOfString,
81
+ raw: raw,
82
+ unstable_getUnpatchedUnencodedVal: getUnpatchedUnencodedVal
83
+ }),
84
+ config: currentConfig
85
+ };
86
+ };
87
+
88
+ /**
89
+ * Loads the Studio's ES module bundle.
90
+ *
91
+ * `next/script` in the Next package; here it is a `<script>` appended to the
92
+ * head, because TanStack Start has no script component and React 19's own
93
+ * script hoisting does not cover `type="module"` — it dedupes `<script async
94
+ * src>` only, and a module script rendered into the tree would be re-inserted
95
+ * on every remount.
96
+ *
97
+ * Deduped by `src` against the document, so the overlay and the studio page
98
+ * can both ask for the same bundle (and a remount can ask again) without
99
+ * loading it twice. Never removed on unmount: the bundle has registered custom
100
+ * elements and event listeners by then, and re-running it is not free.
101
+ */
102
+ function ValScript(_ref) {
103
+ var src = _ref.src;
104
+ useEffect(function () {
105
+ var existing = document.querySelector("script[data-val-script=\"".concat(CSS.escape(src), "\"]"));
106
+ if (existing) {
107
+ return;
108
+ }
109
+ var script = document.createElement("script");
110
+ script.type = "module";
111
+ script.crossOrigin = "anonymous";
112
+ script.src = src;
113
+ script.dataset["valScript"] = src;
114
+ document.head.appendChild(script);
115
+ }, [src]);
116
+ return null;
117
+ }
118
+
119
+ /*
120
+ * See the useConfig.tsx where the config is loaded from session storage
121
+ */
122
+ function useConfigStorageSave(config) {
123
+ React.useEffect(function () {
124
+ sessionStorage.setItem(VAL_CONFIG_SESSION_STORAGE_KEY, JSON.stringify(config));
125
+ }, [config]);
126
+ }
127
+
128
+ function initSessionTheme(config) {
129
+ if (typeof window === "undefined") {
130
+ return null;
131
+ }
132
+ var existingSessionTheme = sessionStorage.getItem(VAL_THEME_SESSION_STORAGE_KEY);
133
+ if (existingSessionTheme && (existingSessionTheme === "dark" || existingSessionTheme === "light")) {
134
+ return existingSessionTheme;
135
+ }
136
+ var storedTheme = localStorage.getItem("val-theme-" + ((config === null || config === void 0 ? void 0 : config.project) || "unknown"));
137
+ var theme = "dark";
138
+ if (storedTheme === "light" || storedTheme === "dark") {
139
+ theme = storedTheme;
140
+ } else if ((config === null || config === void 0 ? void 0 : config.defaultTheme) === "dark" || (config === null || config === void 0 ? void 0 : config.defaultTheme) === "light") {
141
+ theme = config.defaultTheme;
142
+ }
143
+ sessionStorage.setItem(VAL_THEME_SESSION_STORAGE_KEY, theme);
144
+ return theme;
145
+ }
146
+
147
+ // All DOM element that are outside the Shadow DOM should use this class name prefix
148
+ var valPrefixedClass = "val-prefix-" + Internal$1.getSHA256Hash(new TextEncoder().encode(VERSION || "")).slice(0, 8) + "-";
149
+ var prefixStyles = function prefixStyles(styles) {
150
+ return Object.entries(styles).map(function (_ref) {
151
+ var _ref2 = _slicedToArray(_ref, 2),
152
+ key = _ref2[0],
153
+ value = _ref2[1];
154
+ return ".".concat(valPrefixedClass).concat(key.replace(/\//g, "\\/"), " { ").concat(value, " }");
155
+ }).join("\n");
156
+ };
157
+ var cn = function cn(className) {
158
+ return className.map(function (c) {
159
+ return "".concat(valPrefixedClass).concat(c);
160
+ }).join(" ");
161
+ };
162
+
163
+ /**
164
+ * Returns true if the Val Enable cookie is set to "true" in the given cookie
165
+ * string (typically `document.cookie`).
166
+ *
167
+ * Exact-token match: a naive `includes("val_enable=true")` would also match
168
+ * unrelated cookies like `xval_enable=true`. The server sets the cookie to
169
+ * "false" (rather than deleting it) on disable, so the value must be checked
170
+ * too.
171
+ */
172
+ function hasValEnableCookie(cookieString) {
173
+ var _iterator = _createForOfIteratorHelper(cookieString.split(";")),
174
+ _step;
175
+ try {
176
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
177
+ var part = _step.value;
178
+ var eq = part.indexOf("=");
179
+ if (eq === -1) {
180
+ continue;
181
+ }
182
+ var name = part.slice(0, eq).trim();
183
+ var value = part.slice(eq + 1).trim();
184
+ if (name === Internal$1.VAL_ENABLE_COOKIE_NAME && value === "true") {
185
+ return true;
186
+ }
187
+ }
188
+ } catch (err) {
189
+ _iterator.e(err);
190
+ } finally {
191
+ _iterator.f();
192
+ }
193
+ return false;
194
+ }
195
+
196
+ /**
197
+ * Colours for the moments before Val's own stylesheet exists.
198
+ *
199
+ * Two of them, and they are not interchangeable. The studio's loading screen
200
+ * covers the whole viewport and is replaced by the studio's canvas, so it has
201
+ * to match `--bg-canvas`. The loading pill sits on the customer's page as a
202
+ * piece of Val's floating chrome, so it has to match `--bg-float`. Getting
203
+ * them the wrong way round is not invisible: the studio flashes one colour and
204
+ * repaints in another the moment the stylesheet lands.
205
+ *
206
+ * Hardcoded because that is the whole point — these are used exactly when the
207
+ * variables that define them are not loaded yet. They are copies, so they go
208
+ * stale silently: if `--bg-canvas` or `--bg-float` changes in
209
+ * `packages/ui/spa/index.css`, change them here too.
210
+ */
211
+
212
+ /** `--bg-canvas`, dark. The studio's own background. */
213
+ var canvasDarkBg = "#08080a";
214
+ /** `--bg-canvas`, light. */
215
+ var canvasLightBg = "#f4f4f5";
216
+
217
+ /** `--bg-float`, dark. Val's chrome, floating over the customer's page. */
218
+ var floatDarkBg = "#131316";
219
+ /** `--bg-float`, light. */
220
+ var floatLightBg = "#fcfcfc";
221
+
222
+ var SELECTION = "#079455";
223
+ var SELECTION_SOFT = "rgba(7, 148, 85, 0.4)";
224
+
225
+ /**
226
+ * How often the page is re-measured even when nothing said it changed.
227
+ *
228
+ * A backstop, not the mechanism — the observers below carry the responsive
229
+ * case. Slow enough to be free, fast enough that a field which appeared late is
230
+ * in the list before anyone goes looking for it.
231
+ */
232
+ var RESCAN_INTERVAL_MS = 5000;
233
+
234
+ /**
235
+ * The page's half of the canvas protocol.
236
+ *
237
+ * Mounted only in the studio's canvas frame. Its job is to tell the studio
238
+ * where Val's content is on the page, and to do the two things the studio can
239
+ * ask of a document it cannot touch: outline an element, and treat a click as a
240
+ * pick rather than as a click.
241
+ *
242
+ * Nothing here is visible on the customer's own site — this component is not
243
+ * mounted there at all.
244
+ */
245
+ function ValCanvasBridge(_ref) {
246
+ var draftMode = _ref.draftMode,
247
+ isRefreshing = _ref.isRefreshing;
248
+ var _React$useState = React.useState(false),
249
+ _React$useState2 = _slicedToArray(_React$useState, 2),
250
+ picking = _React$useState2[0],
251
+ setPicking = _React$useState2[1];
252
+ var _React$useState3 = React.useState(null),
253
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
254
+ highlighted = _React$useState4[0],
255
+ setHighlighted = _React$useState4[1];
256
+ // Kept in a ref as well, because the capture-phase click listener below is
257
+ // installed once and would otherwise close over the first value forever.
258
+ var pickingRef = React.useRef(false);
259
+ pickingRef.current = picking;
260
+ var post = React.useCallback(function (message) {
261
+ // `*` as the target origin: the studio and the page share an origin today,
262
+ // but the frame does not know the parent's origin for certain and naming
263
+ // the wrong one silently drops the message. Nothing sent here is
264
+ // sensitive — it is the positions of elements on a page the parent is
265
+ // already displaying.
266
+ window.parent.postMessage(message, "*");
267
+ }, []);
268
+
269
+ /**
270
+ * The last payload posted, as a string.
271
+ *
272
+ * The periodic scan below runs whether or not anything moved, and an
273
+ * unchanged payload costs the studio a re-render of the fields column for
274
+ * nothing. Compared as a string because the comparison has to be by value —
275
+ * every scan builds fresh objects.
276
+ */
277
+ var lastPosted = React.useRef(null);
278
+
279
+ /** Every element Val tagged, with where it is on the page. */
280
+ var scan = React.useCallback(function () {
281
+ var elements = [];
282
+ var scrollX = window.scrollX;
283
+ var scrollY = window.scrollY;
284
+ document.querySelectorAll("[data-val-path]").forEach(function (el) {
285
+ if (!(el instanceof HTMLElement) && !(el instanceof SVGElement)) return;
286
+ var attribute = el.getAttribute("data-val-path");
287
+ if (!attribute) return;
288
+ /**
289
+ * A `<source>` has no box of its own, so its `<video>`/`<picture>` parent
290
+ * is measured instead — the same substitution the overlay makes, and for
291
+ * the same reason: an element with a zero-size rect cannot be pointed at.
292
+ */
293
+ var measured = el;
294
+ if (el.tagName === "SOURCE") {
295
+ var parent = el.parentElement;
296
+ if (parent && (parent.tagName === "VIDEO" || parent.tagName === "PICTURE")) {
297
+ measured = parent;
298
+ }
299
+ }
300
+ var rect = measured.getBoundingClientRect();
301
+ if (rect.width === 0 && rect.height === 0) return;
302
+ elements.push({
303
+ // Split by the same helper the rest of Val uses, so the studio gets
304
+ // the paths back in exactly the form it holds them in.
305
+ paths: Internal$1.splitJoinedSourcePaths(attribute),
306
+ // Page coordinates, not viewport: the studio scales and offsets the
307
+ // whole document, so a viewport-relative box would be wrong the moment
308
+ // the frame scrolled.
309
+ rect: {
310
+ top: rect.top + scrollY,
311
+ left: rect.left + scrollX,
312
+ width: rect.width,
313
+ height: rect.height
314
+ }
315
+ });
316
+ });
317
+ var message = {
318
+ val: VAL_CANVAS_MESSAGE,
319
+ type: "elements",
320
+ elements: elements,
321
+ pageSize: {
322
+ width: document.documentElement.scrollWidth,
323
+ height: document.documentElement.scrollHeight
324
+ }
325
+ };
326
+ var serialized = JSON.stringify(message);
327
+ if (serialized === lastPosted.current) {
328
+ return;
329
+ }
330
+ lastPosted.current = serialized;
331
+ post(message);
332
+ }, [post]);
333
+
334
+ // Relayed as it changes, not polled: the studio only has to know the two
335
+ // edges, and a message per render would be a message per keystroke.
336
+ React.useEffect(function () {
337
+ post({
338
+ val: VAL_CANVAS_MESSAGE,
339
+ type: "refreshing",
340
+ pending: isRefreshing === true
341
+ });
342
+ }, [post, isRefreshing]);
343
+
344
+ // Announce the frame, and say whether it is actually showing draft content.
345
+ React.useEffect(function () {
346
+ post({
347
+ val: VAL_CANVAS_MESSAGE,
348
+ type: "ready",
349
+ draftMode: draftMode,
350
+ url: window.location.href
351
+ });
352
+ }, [post, draftMode]);
353
+
354
+ /**
355
+ * Re-scan whenever the page could have moved.
356
+ *
357
+ * Coalesced into one animation frame: a layout change fires mutation,
358
+ * resize and scroll events together, and scanning three times produces three
359
+ * identical messages.
360
+ */
361
+ React.useEffect(function () {
362
+ var frame = null;
363
+ var schedule = function schedule() {
364
+ if (frame !== null) return;
365
+ frame = requestAnimationFrame(function () {
366
+ frame = null;
367
+ scan();
368
+ });
369
+ };
370
+ schedule();
371
+ var mutations = new MutationObserver(schedule);
372
+ mutations.observe(document.body, {
373
+ subtree: true,
374
+ childList: true,
375
+ attributes: true,
376
+ attributeFilter: ["data-val-path", "class", "style"]
377
+ });
378
+ var resizes = new ResizeObserver(schedule);
379
+ resizes.observe(document.documentElement);
380
+ window.addEventListener("scroll", schedule, {
381
+ passive: true
382
+ });
383
+ window.addEventListener("resize", schedule);
384
+ // Images and fonts land after the first paint and move everything below
385
+ // them, so the boxes measured before `load` are stale by definition.
386
+ window.addEventListener("load", schedule);
387
+ return function () {
388
+ if (frame !== null) cancelAnimationFrame(frame);
389
+ mutations.disconnect();
390
+ resizes.disconnect();
391
+ window.removeEventListener("scroll", schedule);
392
+ window.removeEventListener("resize", schedule);
393
+ window.removeEventListener("load", schedule);
394
+ };
395
+ }, [scan]);
396
+
397
+ /**
398
+ * And re-scan every few seconds regardless.
399
+ *
400
+ * The event-driven scan above is not enough in practice, and the way it fails
401
+ * is confusing rather than obviously broken: the fields column shows some of
402
+ * what is on the page and not the rest, until you happen to edit one of the
403
+ * missing ones and they appear. Anything that tags content after the observers
404
+ * are set up but produces no mutation they watch — a streamed-in server
405
+ * component, an element that measured zero because its image had not decoded,
406
+ * content revealed by something outside `data-val-path`, `class` and `style` —
407
+ * is invisible until the next unrelated change. A slow poll costs nothing and
408
+ * closes all of those at once, without having to guess which one it was.
409
+ *
410
+ * Idle-scheduled, and never on the frame's critical path. A scan is a
411
+ * `querySelectorAll` plus a forced layout per tagged element, which is exactly
412
+ * the kind of work that should wait for a gap rather than take one — and this
413
+ * runs inside the customer's own page, where Val is a guest. Skipped entirely
414
+ * while the frame is hidden, where the boxes cannot have moved and nobody is
415
+ * looking at them.
416
+ */
417
+ React.useEffect(function () {
418
+ var idle = null;
419
+ var runWhenIdle = function runWhenIdle() {
420
+ if (document.hidden || idle !== null) return;
421
+ // `requestIdleCallback` where it exists, a timeout where it does not
422
+ // (Safari). The timeout option matters: without it a busy page can defer
423
+ // an idle callback indefinitely, which is the one outcome worse than
424
+ // scanning at a bad moment.
425
+ if (typeof window.requestIdleCallback === "function") {
426
+ idle = window.requestIdleCallback(function () {
427
+ idle = null;
428
+ scan();
429
+ }, {
430
+ timeout: 1000
431
+ });
432
+ } else {
433
+ idle = window.setTimeout(function () {
434
+ idle = null;
435
+ scan();
436
+ }, 0);
437
+ }
438
+ };
439
+ var interval = window.setInterval(runWhenIdle, RESCAN_INTERVAL_MS);
440
+ return function () {
441
+ window.clearInterval(interval);
442
+ if (idle === null) return;
443
+ if (typeof window.cancelIdleCallback === "function") {
444
+ window.cancelIdleCallback(idle);
445
+ } else {
446
+ window.clearTimeout(idle);
447
+ }
448
+ };
449
+ }, [scan]);
450
+
451
+ // What the studio asks for.
452
+ React.useEffect(function () {
453
+ var listener = function listener(event) {
454
+ if (!isValCanvasStudioMessage(event.data)) return;
455
+ var message = event.data;
456
+ if (message.type === "rescan") {
457
+ scan();
458
+ return;
459
+ }
460
+ if (message.type === "setPicking") {
461
+ setPicking(message.picking);
462
+ return;
463
+ }
464
+ if (message.type === "sourceUpdate") {
465
+ /**
466
+ * Handed straight to the listener the page already has.
467
+ *
468
+ * `ValTanStackProvider` listens for this event on its own window to make an
469
+ * inline edit visible behind the overlay; the only thing different here
470
+ * is that the edit was made in another window. Re-dispatching rather
471
+ * than reaching into the store keeps one path into the page — including
472
+ * the `router.invalidate()` it throttles behind it, which is what brings a
473
+ * server component's own re-read across.
474
+ */
475
+ window.dispatchEvent(new CustomEvent("val-event", {
476
+ detail: {
477
+ type: "source-update",
478
+ moduleFilePath: message.moduleFilePath,
479
+ source: message.source
480
+ }
481
+ }));
482
+ return;
483
+ }
484
+ if (message.type === "sourcesSynced") {
485
+ // Relayed the same way and for the same reason as `sourceUpdate`: the
486
+ // page is listening on its own window, and the studio is in another.
487
+ window.dispatchEvent(new CustomEvent("val-event", {
488
+ detail: {
489
+ type: "sources-synced"
490
+ }
491
+ }));
492
+ return;
493
+ }
494
+ setHighlighted(message.path);
495
+ if (message.path !== null && message.scrollIntoView) {
496
+ var target = findByPath(message.path);
497
+ // NOT `scrollIntoView` — see `scrollWithinPage`.
498
+ if (target !== null) scrollWithinPage(target);
499
+ }
500
+ };
501
+ window.addEventListener("message", listener);
502
+ return function () {
503
+ return window.removeEventListener("message", listener);
504
+ };
505
+ }, [scan]);
506
+
507
+ /**
508
+ * Picking.
509
+ *
510
+ * Capture phase, and the event is stopped: the point of picking mode is that
511
+ * a click means "this one" rather than what the page would do with it, and a
512
+ * link that navigates has taken the canvas somewhere else before the studio
513
+ * hears anything.
514
+ */
515
+ React.useEffect(function () {
516
+ var onClick = function onClick(event) {
517
+ if (!pickingRef.current) return;
518
+ var target = event.target;
519
+ if (!(target instanceof Element)) return;
520
+ var tagged = target.closest("[data-val-path]");
521
+ var attribute = tagged === null || tagged === void 0 ? void 0 : tagged.getAttribute("data-val-path");
522
+ if (!attribute) return;
523
+ event.preventDefault();
524
+ event.stopPropagation();
525
+ post({
526
+ val: VAL_CANVAS_MESSAGE,
527
+ type: "clicked",
528
+ paths: Internal$1.splitJoinedSourcePaths(attribute)
529
+ });
530
+ };
531
+ document.addEventListener("click", onClick, {
532
+ capture: true
533
+ });
534
+ return function () {
535
+ return document.removeEventListener("click", onClick, {
536
+ capture: true
537
+ });
538
+ };
539
+ }, [post]);
540
+
541
+ /**
542
+ * Two-finger gestures, handed to the studio.
543
+ *
544
+ * The window around the page is the studio's, and the page is a frame: a
545
+ * finger that lands here is this document's and the studio never hears about
546
+ * it. So a pinch on the page could not zoom the window, which on a phone is
547
+ * the only zoom there is — the toolbar's + and - are a poor substitute for the
548
+ * gesture everyone already makes.
549
+ *
550
+ * Two fingers and only two. One finger is the page's: it scrolls it, taps its
551
+ * links, drags whatever the page put there. Two are the window's, for zooming
552
+ * and for moving the page around inside it. Nothing has to be moded, and the
553
+ * two cannot be confused for one another.
554
+ *
555
+ * `passive: false` because both handlers cancel: the default for two fingers
556
+ * is the browser's own page zoom, which inside a frame the studio is already
557
+ * scaling produces two zooms fighting over one gesture.
558
+ */
559
+ React.useEffect(function () {
560
+ var midpoint = function midpoint(touches) {
561
+ return {
562
+ x: (touches[0].clientX + touches[1].clientX) / 2,
563
+ y: (touches[0].clientY + touches[1].clientY) / 2
564
+ };
565
+ };
566
+ var span = function span(touches) {
567
+ return Math.hypot(touches[0].clientX - touches[1].clientX, touches[0].clientY - touches[1].clientY);
568
+ };
569
+ /**
570
+ * Whether a gesture is in progress.
571
+ *
572
+ * `touchend` fires per finger, so lifting one of two leaves a `touchend`
573
+ * carrying a single touch — which is the end of the gesture — and the
574
+ * second leaves one carrying none. Without this the second would post a
575
+ * second `end` for a gesture that was already over.
576
+ */
577
+ var active = false;
578
+ var onStart = function onStart(event) {
579
+ if (event.touches.length !== 2) return;
580
+ event.preventDefault();
581
+ active = true;
582
+ post({
583
+ val: VAL_CANVAS_MESSAGE,
584
+ type: "pinch",
585
+ phase: "start",
586
+ span: span(event.touches),
587
+ center: midpoint(event.touches)
588
+ });
589
+ };
590
+ var onMove = function onMove(event) {
591
+ if (event.touches.length !== 2 || !active) return;
592
+ event.preventDefault();
593
+ post({
594
+ val: VAL_CANVAS_MESSAGE,
595
+ type: "pinch",
596
+ phase: "move",
597
+ span: span(event.touches),
598
+ center: midpoint(event.touches)
599
+ });
600
+ };
601
+ var onEnd = function onEnd(event) {
602
+ if (!active || event.touches.length >= 2) return;
603
+ active = false;
604
+ post({
605
+ val: VAL_CANVAS_MESSAGE,
606
+ type: "pinch",
607
+ phase: "end",
608
+ span: 0,
609
+ center: {
610
+ x: 0,
611
+ y: 0
612
+ }
613
+ });
614
+ };
615
+ document.addEventListener("touchstart", onStart, {
616
+ passive: false
617
+ });
618
+ document.addEventListener("touchmove", onMove, {
619
+ passive: false
620
+ });
621
+ document.addEventListener("touchend", onEnd);
622
+ document.addEventListener("touchcancel", onEnd);
623
+ return function () {
624
+ document.removeEventListener("touchstart", onStart);
625
+ document.removeEventListener("touchmove", onMove);
626
+ document.removeEventListener("touchend", onEnd);
627
+ document.removeEventListener("touchcancel", onEnd);
628
+ };
629
+ }, [post]);
630
+
631
+ /**
632
+ * And ctrl/cmd + wheel, which is what a trackpad pinch reports as.
633
+ *
634
+ * The same problem as touches and the same answer: the wheel event lands on
635
+ * this document, so without relaying it a trackpad pinch over the page does
636
+ * nothing while the identical gesture over the canvas background zooms.
637
+ *
638
+ * A PLAIN wheel is deliberately left alone — that is the page scrolling
639
+ * itself, which is exactly what it should do.
640
+ */
641
+ React.useEffect(function () {
642
+ var onWheel = function onWheel(event) {
643
+ if (!event.ctrlKey && !event.metaKey) return;
644
+ event.preventDefault();
645
+ post({
646
+ val: VAL_CANVAS_MESSAGE,
647
+ type: "zoom",
648
+ // The same response curve the studio uses for a wheel on its own
649
+ // background, so the gesture feels the same on both sides of the frame.
650
+ factor: 1 - event.deltaY / 300,
651
+ center: {
652
+ x: event.clientX,
653
+ y: event.clientY
654
+ }
655
+ });
656
+ };
657
+ document.addEventListener("wheel", onWheel, {
658
+ passive: false
659
+ });
660
+ return function () {
661
+ return document.removeEventListener("wheel", onWheel);
662
+ };
663
+ }, [post]);
664
+
665
+ /**
666
+ * The outlines, as a stylesheet rather than as injected elements.
667
+ *
668
+ * A style rule cannot disturb the page's layout, which an absolutely
669
+ * positioned box on top of it eventually would — and the whole value of
670
+ * showing the real page is that it is laid out the way the customer built
671
+ * it. `outline` rather than `border` for the same reason: outlines do not
672
+ * take up space.
673
+ */
674
+ return /*#__PURE__*/jsx("style", {
675
+ // The attribute selector is built from the path, so it is escaped as a
676
+ // CSS string. A path contains quotes (`?p="/"`) and would otherwise
677
+ // close the selector early.
678
+ dangerouslySetInnerHTML: {
679
+ __html: [picking ? "[data-val-path] { outline: 1px solid ".concat(SELECTION_SOFT, "; outline-offset: 1px; cursor: pointer; }\n[data-val-path]:hover { outline: 2px solid ").concat(SELECTION, "; }") : "", highlighted ? highlightRule(highlighted) : ""].filter(Boolean).join("\n")
680
+ }
681
+ });
682
+ }
683
+
684
+ /**
685
+ * A rule that outlines exactly the elements carrying `path`.
686
+ *
687
+ * The attribute holds a comma-separated list, so this cannot be one `=`
688
+ * comparison; and it cannot be a `*=` substring test either, because a path is
689
+ * a prefix of every path below it. `?p="/blogs/blog1"` is contained in
690
+ * `?p="/blogs/blog1"."title"`, so a substring test on the route would outline
691
+ * every field on the page — which is what it did.
692
+ *
693
+ * Four selectors instead: the whole attribute, the first item, the last item,
694
+ * and one in the middle. Between them they match the path as a complete list
695
+ * item and nothing else.
696
+ */
697
+ function highlightRule(path) {
698
+ var value = JSON.stringify(path);
699
+ var inner = JSON.stringify(",".concat(path, ","));
700
+ var first = JSON.stringify("".concat(path, ","));
701
+ var last = JSON.stringify(",".concat(path));
702
+ var selectors = ["[data-val-path=".concat(value, "]"), "[data-val-path^=".concat(first, "]"), "[data-val-path$=".concat(last, "]"), "[data-val-path*=".concat(inner, "]")].join(",\n");
703
+ return "".concat(selectors, " { outline: 2px solid ").concat(SELECTION, "; outline-offset: 1px; }");
704
+ }
705
+
706
+ /**
707
+ * Bring an element into view WITHOUT scrolling anything outside this page.
708
+ *
709
+ * `Element.scrollIntoView` would be the obvious thing, and it is the thing that
710
+ * broke the studio. It scrolls every scroll container between the element and
711
+ * the viewport, and for a same-origin frame that chain does not stop at the
712
+ * frame: it continues into the embedder and scrolls the studio's own containers
713
+ * too. Measured in Chromium — with the studio's phone panes placed on the
714
+ * editor, a single `scrollIntoView` in here pulls them back onto the canvas, and
715
+ * because it reveals the element rather than the frame it can leave them
716
+ * anywhere in between, showing half of each.
717
+ *
718
+ * That is not a thing the page has any business doing. It was asked to show one
719
+ * of its own elements; where the studio is looking is the studio's affair. So
720
+ * the scrolling is done by hand, one scrollport at a time, and the walk stops at
721
+ * this document.
722
+ *
723
+ * Only the document centres the target vertically — it is being pointed out,
724
+ * and something flush against the top edge of the fold does not read as pointed
725
+ * out. Every scrollport inside the page is moved as little as it takes to bring
726
+ * the target inside it: a carousel, a sticky sidebar or a sideways-scrolling
727
+ * table is showing what it is showing on purpose, and re-centring one that did
728
+ * not need moving is the page rearranging itself around a highlight.
729
+ */
730
+ function scrollWithinPage(target) {
731
+ var node = target.parentElement;
732
+ while (node !== null) {
733
+ var isRoot = node === document.body || node === document.documentElement;
734
+ if (isRoot) break;
735
+ if (isScrollable(node)) {
736
+ var port = node.getBoundingClientRect();
737
+ var _box = target.getBoundingClientRect();
738
+ node.scrollTop += delta(_box.top - port.top, _box.height, node.clientHeight, false);
739
+ node.scrollLeft += delta(_box.left - port.left, _box.width, node.clientWidth, false);
740
+ }
741
+ node = node.parentElement;
742
+ }
743
+ // And this document, which is the last scrollport there is as far as the page
744
+ // is concerned. `window`, not `documentElement.scrollIntoView`-by-another-name:
745
+ // this cannot reach past the frame.
746
+ var box = target.getBoundingClientRect();
747
+ window.scrollTo({
748
+ top: Math.max(0, window.scrollY + delta(box.top, box.height, window.innerHeight, true)),
749
+ left: Math.max(0, window.scrollX + delta(box.left, box.width, window.innerWidth, false)),
750
+ behavior: "smooth"
751
+ });
752
+ }
753
+
754
+ /**
755
+ * How far a scrollport has to move for `size` at `offset` to be shown.
756
+ *
757
+ * `center` asks for the middle of the port; without it the answer is zero
758
+ * whenever the thing is already inside, which is what keeps a scrollport that
759
+ * did not need moving from being disturbed.
760
+ */
761
+ function delta(offset, size, port, center) {
762
+ if (center) return offset - (port - size) / 2;
763
+ if (offset < 0) return offset;
764
+ if (offset + size > port) return Math.min(offset, offset + size - port);
765
+ return 0;
766
+ }
767
+
768
+ /** Whether this element scrolls its own content, rather than growing with it. */
769
+ function isScrollable(el) {
770
+ var style = getComputedStyle(el);
771
+ var scrolls = function scrolls(value) {
772
+ return value === "auto" || value === "scroll" || value === "overlay";
773
+ };
774
+ return scrolls(style.overflowY) && el.scrollHeight > el.clientHeight || scrolls(style.overflowX) && el.scrollWidth > el.clientWidth;
775
+ }
776
+ function findByPath(path) {
777
+ var elements = document.querySelectorAll("[data-val-path]");
778
+ for (var _i = 0, _Array$from = Array.from(elements); _i < _Array$from.length; _i++) {
779
+ var el = _Array$from[_i];
780
+ var attribute = el.getAttribute("data-val-path");
781
+ if (!attribute) continue;
782
+ // Compared as strings: the split returns branded `SourcePath`s and what
783
+ // came back over `postMessage` is a plain string, which is the same value.
784
+ if (Internal$1.splitJoinedSourcePaths(attribute).some(function (candidate) {
785
+ return candidate === path;
786
+ })) {
787
+ return el;
788
+ }
789
+ }
790
+ return null;
791
+ }
792
+
793
+ /**
794
+ * Whether the page should re-read the server on its own, right now.
795
+ *
796
+ * The page cannot know when its own edit has been persisted. An edit is applied
797
+ * to the client store immediately and written to the server asynchronously, so
798
+ * a `router.invalidate()` fired on the edit can reach a server that has not saved
799
+ * the patch yet: the payload comes back with the old content, and nothing
800
+ * schedules another look. What an editor sees is a canvas that flickered and did
801
+ * not change — which is indistinguishable from an edit that did not work.
802
+ *
803
+ * So the page asks again a little later. This is the "should I bother" half of
804
+ * that, kept out of the effect and pure so the reasons NOT to are checkable:
805
+ * every one of them is a whole-route request avoided, and in development Next
806
+ * re-renders (and sometimes recompiles) the page for each one.
807
+ */
808
+ function shouldSafetyRefresh(state) {
809
+ // Never edited: a preview someone is only looking at has nothing to catch up
810
+ // on, and polling it would cost a route render per tick for nothing.
811
+ if (state.lastEditAt === 0) return false;
812
+ // The editing stopped long enough ago that anything in flight has landed.
813
+ if (state.now - state.lastEditAt > state.windowMs) return false;
814
+ // Nobody is looking. The caller refreshes once on the way back instead.
815
+ if (state.hidden) return false;
816
+ // Already on its way. Stacking whole-route requests makes the slowness this
817
+ // exists to paper over worse, not better.
818
+ if (state.isRefreshing) return false;
819
+ // One just went out — usually the edit-driven refresh, a moment ago.
820
+ if (state.now - state.lastRefreshAt < state.minIntervalMs) return false;
821
+ return true;
822
+ }
823
+
824
+ var REFRESH_INTERVAL_MS = 500;
825
+
826
+ /**
827
+ * How often the page re-reads the server on its own, without being told to.
828
+ *
829
+ * A safety net for a race no amount of care on this side can close. An edit is
830
+ * applied to the client store immediately and saved to the server
831
+ * asynchronously, so `router.invalidate()` fired on the edit can reach a server
832
+ * that has not written the patch yet: the loaders come back with the old
833
+ * content, the counter has already been cleared, and nothing schedules another
834
+ * look. What you see is a canvas that flickered and did not change — which is
835
+ * indistinguishable from an edit that did not work.
836
+ *
837
+ * The honest fix would be for the page to know when its patch has been
838
+ * persisted, and it has no way to ask. So it asks again a little later instead.
839
+ */
840
+ var SAFETY_REFRESH_MS = 10000;
841
+
842
+ /**
843
+ * How long after an edit the safety net keeps looking.
844
+ *
845
+ * The reason to stop is that this re-runs every loader on the page: in
846
+ * development that is a server round trip and a recompile, and a preview
847
+ * nobody is editing has nothing to catch up on. So the net is armed by editing
848
+ * and disarms itself when the editing stops — long enough after the last
849
+ * keystroke that any write still in flight has certainly landed.
850
+ */
851
+ var SAFETY_REFRESH_WINDOW_MS = 30000;
852
+
853
+ /**
854
+ * Fired on this window when an edit has been applied to the client store.
855
+ *
856
+ * The refresh loop listens for it so it can start the round trip immediately
857
+ * rather than on its next tick. A DOM event rather than state, because the
858
+ * counter it accompanies is a ref and the loop lives in an effect that must not
859
+ * be torn down and rebuilt per keystroke.
860
+ */
861
+ var VAL_EDIT_LANDED = "val-edit-landed";
862
+
863
+ /**
864
+ * How often `/draft/stat` is asked when nothing is in progress.
865
+ *
866
+ * Draft mode changes when someone toggles it, which the handshake below already
867
+ * covers — so this is only there to notice it being changed somewhere else.
868
+ */
869
+ var DRAFT_IDLE_POLL_MS = 20000;
870
+
871
+ /**
872
+ * And how often while the enable/disable handshake is in flight.
873
+ *
874
+ * Fast on purpose: the hidden iframe redirects through `/draft/enable`, and the
875
+ * page cannot do anything useful until that has landed.
876
+ */
877
+ var DRAFT_HANDSHAKE_POLL_MS = 100;
878
+
879
+ /**
880
+ * How long that fast phase may last before it gives up.
881
+ *
882
+ * Without a deadline it does not end: the only thing that clears `iframeSrc` is
883
+ * a `val-ready` message posted by the redirect target, so a frame that fails to
884
+ * load — a 500, an auth redirect, a dev server still compiling — left the page
885
+ * asking ten times a second, forever. A browser runs about six connections per
886
+ * origin, so that is enough to push `/stat`, `/patches` and the canvas document
887
+ * itself into the browser's own queue, where they are indistinguishable from a
888
+ * slow server.
889
+ *
890
+ * Generous enough for a first compile in the dev server, which is the slowest case
891
+ * that is not a fault.
892
+ */
893
+ var DRAFT_HANDSHAKE_TIMEOUT_MS = 10000;
894
+ var ValTanStackProvider = function ValTanStackProvider(props) {
895
+ var _draftModeReady$curre2;
896
+ // TODO: use config:
897
+ var route = "/api/val";
898
+ /**
899
+ * Loaded on first use, never at import time.
900
+ *
901
+ * `createValClient` validates every request and response against the zod
902
+ * schemas in `@valbuild/shared/internal`'s `ApiRoutes`, and preconstruct
903
+ * publishes that entrypoint as one module - so importing it statically put
904
+ * ~113 KB of zod in the bundle of every page of every Val site. The only
905
+ * caller is the draft-mode poll below, which returns early unless
906
+ * `mountOverlay` is set, so a visitor without the Val Enable cookie never
907
+ * triggers this import and never downloads that chunk.
908
+ */
909
+ var clientRef = React.useRef(null);
910
+ var configRef = React.useRef(props.config);
911
+ configRef.current = props.config;
912
+ var getClient = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
913
+ var _yield$import, createValClient;
914
+ return _regenerator().w(function (_context) {
915
+ while (1) switch (_context.n) {
916
+ case 0:
917
+ if (clientRef.current) {
918
+ _context.n = 2;
919
+ break;
920
+ }
921
+ _context.n = 1;
922
+ return import('@valbuild/shared/internal');
923
+ case 1:
924
+ _yield$import = _context.v;
925
+ createValClient = _yield$import.createValClient;
926
+ clientRef.current = createValClient(route, _objectSpread2(_objectSpread2({}, configRef.current), {}, {
927
+ contentHostUrl: DEFAULT_CONTENT_HOST
928
+ }));
929
+ case 2:
930
+ return _context.a(2, clientRef.current);
931
+ }
932
+ }, _callee);
933
+ })), [route]);
934
+
935
+ // TODO: move below into react package
936
+ var valStore = React.useMemo(function () {
937
+ return new ValExternalStore();
938
+ }, []);
939
+ // Whether useValStega should actually suspend. False during SSR and the
940
+ // hydration render — the server store is never populated (draft data
941
+ // arrives via browser CustomEvents only), so suspending there would just
942
+ // stall into the waitForLoad timeout, and hydration must render the static
943
+ // source so it matches the server HTML exactly. Activated post-hydration
944
+ // (in an effect, only when the Val Enable cookie is present) inside a
945
+ // transition: React keeps the static content visible while hooks suspend
946
+ // and then swaps to draft data as a normal update — no Suspense fallback
947
+ // flash and no hydration mismatch.
948
+ var _React$useState = React.useState(false),
949
+ _React$useState2 = _slicedToArray(_React$useState, 2),
950
+ suspendActive = _React$useState2[0],
951
+ setSuspendActive = _React$useState2[1];
952
+ /**
953
+ * Whether the studio has said it sent everything it holds.
954
+ *
955
+ * Until then, a module missing from the store might still be on its way. After
956
+ * it, a missing module simply has no draft — see `sourcesSynced` in the canvas
957
+ * protocol. Without this the page could only wait out `waitForLoad`'s timeout,
958
+ * ten seconds per module nobody had edited.
959
+ */
960
+ var _React$useState3 = React.useState(false),
961
+ _React$useState4 = _slicedToArray(_React$useState3, 2),
962
+ draftSourcesSynced = _React$useState4[0],
963
+ setDraftSourcesSynced = _React$useState4[1];
964
+ var _React$useState5 = React.useState(),
965
+ _React$useState6 = _slicedToArray(_React$useState5, 2),
966
+ mountOverlay = _React$useState6[0],
967
+ setMountOverlay = _React$useState6[1];
968
+ /**
969
+ * Whether this document is the studio's canvas frame.
970
+ *
971
+ * The canvas needs everything `mountOverlay` turns on — draft content,
972
+ * `data-val-path` on every tracked element, the refresh loop that brings an
973
+ * edit across — and none of what it *shows*, because the studio around the
974
+ * frame is already that UI. So it is a second flag rather than a different
975
+ * value of the first one: two overlays on one screen, one of them inside the
976
+ * other, is the thing to avoid.
977
+ *
978
+ * Read once, in the same effect as `mountOverlay`: it is a property of how
979
+ * the document was loaded, and it cannot change without a navigation.
980
+ */
981
+ var _React$useState7 = React.useState(false),
982
+ _React$useState8 = _slicedToArray(_React$useState7, 2),
983
+ isCanvas = _React$useState8[0],
984
+ setIsCanvas = _React$useState8[1];
985
+ var _React$useState9 = React.useState(null),
986
+ _React$useState0 = _slicedToArray(_React$useState9, 2),
987
+ draftMode = _React$useState0[0],
988
+ setDraftMode = _React$useState0[1];
989
+ /**
990
+ * Resolves when `draftMode` stops being unknown.
991
+ *
992
+ * `null` is the state before `/draft/stat` has answered, and it is NOT a
993
+ * synonym for "off": the reader that turns a selector into content treats it
994
+ * as off, so a render that happens while draft mode is still unknown resolves
995
+ * against committed source. For a route that only exists in an uncommitted
996
+ * patch that means `notFound()` — terminal, before the answer even arrives.
997
+ *
998
+ * So a page that is going to suspend waits for the answer first. A promise
999
+ * rather than a re-render, because the thing that has to wait is a render.
1000
+ */
1001
+ var draftModeReady = React.useRef(null);
1002
+ // Only for a page that opted in. A visitor with Val off never reads it, and
1003
+ // an object created per mount for nobody is the kind of cost that is invisible
1004
+ // until it is in every page of every app.
1005
+ if (props.suspend && draftModeReady.current === null) {
1006
+ var resolve = function resolve() {
1007
+ return undefined;
1008
+ };
1009
+ var promise = new Promise(function (r) {
1010
+ resolve = r;
1011
+ });
1012
+ draftModeReady.current = {
1013
+ promise: promise,
1014
+ resolve: resolve
1015
+ };
1016
+ }
1017
+ React.useEffect(function () {
1018
+ if (draftMode !== null) {
1019
+ var _draftModeReady$curre;
1020
+ (_draftModeReady$curre = draftModeReady.current) === null || _draftModeReady$curre === void 0 || _draftModeReady$curre.resolve();
1021
+ }
1022
+ }, [draftMode]);
1023
+ var _React$useState1 = React.useState(false),
1024
+ _React$useState10 = _slicedToArray(_React$useState1, 2),
1025
+ spaReady = _React$useState10[0],
1026
+ 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...
1027
+ var router = useRouter();
1028
+ var _React$useTransition = React.useTransition(),
1029
+ _React$useTransition2 = _slicedToArray(_React$useTransition, 2),
1030
+ isRefreshing = _React$useTransition2[0],
1031
+ startTransition = _React$useTransition2[1];
1032
+ /**
1033
+ * The same answer as `isRefreshing`, readable from the refresh effect.
1034
+ *
1035
+ * The effect must not be rebuilt every time a refresh starts and ends — it
1036
+ * owns the timers and the "when did we last edit" bookkeeping, and tearing
1037
+ * that down mid-flurry loses it. A ref updated on render is the value without
1038
+ * the dependency.
1039
+ */
1040
+ var isRefreshingRef = React.useRef(false);
1041
+ var rerenderCounterRef = React.useRef(0);
1042
+ var _React$useState11 = React.useState(null),
1043
+ _React$useState12 = _slicedToArray(_React$useState11, 2),
1044
+ iframeSrc = _React$useState12[0],
1045
+ setIframeSrc = _React$useState12[1];
1046
+ var pathname = useRouterState({
1047
+ select: function select(state) {
1048
+ return state.location.pathname;
1049
+ }
1050
+ });
1051
+ isRefreshingRef.current = isRefreshing;
1052
+ useEffect(function () {
1053
+ window.dispatchEvent(new CustomEvent("val-provider:pathname", {
1054
+ detail: pathname
1055
+ }));
1056
+ }, [pathname]);
1057
+ useConsoleLogEnableVal(mountOverlay);
1058
+ React.useEffect(function () {
1059
+ if (location.search === "?message_onready=true") {
1060
+ console.warn("Val is verifying draft mode...");
1061
+ return;
1062
+ }
1063
+ if (isValStudioPath(location.pathname)) {
1064
+ setMountOverlay(false);
1065
+ return;
1066
+ }
1067
+ setIsCanvas(isValCanvasFrame(location.search));
1068
+ setMountOverlay(hasValEnableCookie(document.cookie));
1069
+ }, []);
1070
+ React.useEffect(function () {
1071
+ // Activate the Suspense gate after hydration. Inside a transition so
1072
+ // already-visible (static) content stays on screen while useValStega
1073
+ // suspends — no fallback flash — and the swap to draft data commits as a
1074
+ // normal update instead of a hydration mismatch. Never deactivated:
1075
+ // components must not stop suspending across renders (the draft-mode-off
1076
+ // release valve lives in useValStega instead).
1077
+ /*
1078
+ * Gated on `mountOverlay`, not on the cookie directly — the ORDER is the
1079
+ * point.
1080
+ *
1081
+ * `useValStega` suspends on `draftModeReady`, and the only thing that ever
1082
+ * resolves that is the `/draft/stat` poll below, which does not start until
1083
+ * `mountOverlay` is set. Activating the gate in the same effect flush that
1084
+ * sets `mountOverlay` means the tree can suspend before that state has
1085
+ * committed, and then it never does: React was left re-rendering a
1086
+ * suspended tree in which `mountOverlay` is forever `undefined`, so the
1087
+ * poll never ran, so the promise never resolved. The page hydrated and then
1088
+ * simply stopped — no overlay, no draft content, no error.
1089
+ *
1090
+ * Reading `mountOverlay` instead makes the dependency explicit: the poll is
1091
+ * running by the time anything is allowed to wait on it.
1092
+ */
1093
+ if (props.suspend && mountOverlay) {
1094
+ startTransition(function () {
1095
+ setSuspendActive(true);
1096
+ });
1097
+ }
1098
+ }, [props.suspend, mountOverlay]);
1099
+
1100
+ /**
1101
+ * Re-render the server tree when an edit lands, then at most every 500ms.
1102
+ *
1103
+ * Leading edge, which is the whole point. This polled every 500ms and only
1104
+ * refreshed on the tick, so a keystroke waited up to half a second before the
1105
+ * round trip even began — pure dead time on top of a request that is already
1106
+ * the slow part in development. Refreshing on arrival and *then* enforcing the
1107
+ * gap keeps the same ceiling on requests while removing the wait.
1108
+ *
1109
+ * Still a counter rather than a boolean: edits that arrive during the gap have
1110
+ * to be picked up by the next refresh, and the count is what says whether
1111
+ * there are any.
1112
+ */
1113
+ React.useEffect(function () {
1114
+ if (!mountOverlay || props.disableRefresh) {
1115
+ return;
1116
+ }
1117
+ var timer = null;
1118
+ var lastRefreshAt = 0;
1119
+ /** When an edit last landed here. 0 means this page has never been edited. */
1120
+ var lastEditAt = 0;
1121
+ /** Whether a safety tick was skipped because nobody was looking. */
1122
+ var missedWhileHidden = false;
1123
+ var refresh = function refresh() {
1124
+ rerenderCounterRef.current = 0;
1125
+ lastRefreshAt = Date.now();
1126
+ startTransition(function () {
1127
+ /*
1128
+ * Re-run the loaders of every match, then re-render.
1129
+ *
1130
+ * TanStack's equivalent of `router.refresh()`. `invalidate()` marks the
1131
+ * current matches stale and re-runs their loaders — which is where a
1132
+ * TanStack Start app reads Val content on the server — so this is the
1133
+ * round trip that brings an edit across. It resolves when the loaders
1134
+ * have settled, which is what keeps `isRefreshing` honest.
1135
+ */
1136
+ void router.invalidate();
1137
+ });
1138
+ };
1139
+ var _tick = function tick() {
1140
+ timer = null;
1141
+ if (rerenderCounterRef.current === 0) return;
1142
+ var since = Date.now() - lastRefreshAt;
1143
+ if (since >= REFRESH_INTERVAL_MS) {
1144
+ refresh();
1145
+ } else {
1146
+ timer = setTimeout(_tick, REFRESH_INTERVAL_MS - since);
1147
+ }
1148
+ };
1149
+ var onEdit = function onEdit() {
1150
+ lastEditAt = Date.now();
1151
+ if (timer !== null) return;
1152
+ timer = setTimeout(_tick, 0);
1153
+ };
1154
+ window.addEventListener(VAL_EDIT_LANDED, onEdit);
1155
+ // A backstop for the counter being bumped without the event — the two are
1156
+ // incremented in the same places today, but the poll is what made this
1157
+ // correct regardless of who bumped it.
1158
+ var interval = setInterval(_tick, REFRESH_INTERVAL_MS);
1159
+
1160
+ /**
1161
+ * The safety net: look again, whether or not anything told us to.
1162
+ *
1163
+ * The reasons not to are in `shouldSafetyRefresh`, where they can be tested.
1164
+ * `hidden` is the one this side has to remember something about: a skipped
1165
+ * tick is worth one look when the tab comes back.
1166
+ */
1167
+ var safetyTick = function safetyTick() {
1168
+ if (document.hidden) {
1169
+ missedWhileHidden = true;
1170
+ return;
1171
+ }
1172
+ var go = shouldSafetyRefresh({
1173
+ now: Date.now(),
1174
+ lastEditAt: lastEditAt,
1175
+ lastRefreshAt: lastRefreshAt,
1176
+ hidden: false,
1177
+ isRefreshing: isRefreshingRef.current,
1178
+ windowMs: SAFETY_REFRESH_WINDOW_MS,
1179
+ minIntervalMs: REFRESH_INTERVAL_MS
1180
+ });
1181
+ if (go) refresh();
1182
+ };
1183
+ var safety = setInterval(safetyTick, SAFETY_REFRESH_MS);
1184
+ /**
1185
+ * Coming back to the tab is worth one look.
1186
+ *
1187
+ * Otherwise a background tab skips its ticks and then has to wait out a
1188
+ * whole interval on return — which is exactly the moment someone is looking
1189
+ * at the page to see whether their edit took.
1190
+ */
1191
+ var onVisibilityChange = function onVisibilityChange() {
1192
+ if (document.hidden || !missedWhileHidden) return;
1193
+ missedWhileHidden = false;
1194
+ safetyTick();
1195
+ };
1196
+ document.addEventListener("visibilitychange", onVisibilityChange);
1197
+ return function () {
1198
+ window.removeEventListener(VAL_EDIT_LANDED, onEdit);
1199
+ document.removeEventListener("visibilitychange", onVisibilityChange);
1200
+ clearInterval(interval);
1201
+ clearInterval(safety);
1202
+ if (timer !== null) clearTimeout(timer);
1203
+ };
1204
+ }, [mountOverlay, props.disableRefresh]);
1205
+ React.useEffect(function () {
1206
+ if (!mountOverlay) {
1207
+ return;
1208
+ }
1209
+ if (draftMode === null) {
1210
+ return;
1211
+ }
1212
+ var valProviderOverlayListener = function valProviderOverlayListener(event) {
1213
+ if (event instanceof CustomEvent) {
1214
+ if (!(event !== null && event !== void 0 && event.detail.type)) {
1215
+ console.error("Val: invalid event detail (val-overlay-provider)", event);
1216
+ }
1217
+ if (event.detail.type === "spa-ready") {
1218
+ setSpaReady(true);
1219
+ } else if (event.detail.type === "draftMode" && (typeof event.detail.value === "boolean" || event.detail.value === null)) {
1220
+ var _draftMode = event.detail.value;
1221
+ if (_draftMode === true) {
1222
+ setIframeSrc(function (prev) {
1223
+ if (prev === null) {
1224
+ return "".concat(route, "/draft/enable?redirect_to=").concat(encodeURIComponent(window.location.origin + "/val?message_onready=true"));
1225
+ }
1226
+ return prev;
1227
+ });
1228
+ } else if (_draftMode === false) {
1229
+ setIframeSrc(function (prev) {
1230
+ if (prev === null) {
1231
+ return "".concat(route, "/draft/disable?redirect_to=").concat(encodeURIComponent(window.location.origin + "/val?message_onready=true"));
1232
+ }
1233
+ return prev;
1234
+ });
1235
+ }
1236
+ } else {
1237
+ console.error("Val: invalid event detail (val-overlay-provider)", event.detail);
1238
+ }
1239
+ } else {
1240
+ console.error("Val: invalid event (val-overlay-provider)", event);
1241
+ }
1242
+ };
1243
+ window.addEventListener("val-overlay-provider", valProviderOverlayListener);
1244
+ return function () {
1245
+ window.removeEventListener("val-overlay-provider", valProviderOverlayListener);
1246
+ };
1247
+ }, [mountOverlay, draftMode]);
1248
+ var pollDraftStatIdRef = React.useRef(0);
1249
+ React.useEffect(function () {
1250
+ // continuous polling to check for updates:
1251
+
1252
+ var timeout;
1253
+ function pollCurrentDraftMode() {
1254
+ if (!mountOverlay) {
1255
+ return;
1256
+ }
1257
+ window.dispatchEvent(new CustomEvent("val-overlay-spa", {
1258
+ detail: {
1259
+ type: "draftModeLoading",
1260
+ value: iframeSrc !== null
1261
+ }
1262
+ }));
1263
+ var pollDraftStatId = ++pollDraftStatIdRef.current;
1264
+ getClient().then(function (client) {
1265
+ return client("/draft/stat", "GET", {});
1266
+ }).then(function (res) {
1267
+ if (pollDraftStatIdRef.current !== pollDraftStatId) {
1268
+ return;
1269
+ }
1270
+ if (res.status === null) {
1271
+ // ignore network errors
1272
+ return;
1273
+ }
1274
+ if (res.status === 401) {
1275
+ // Not authorized (e.g. stale Val Enable cookie after the session
1276
+ // expired): treat draft mode as off so useValStega's Suspense gate
1277
+ // is released instead of leaving draftMode stuck at null and
1278
+ // re-suspending into the waitForLoad timeout.
1279
+ setDraftMode(false);
1280
+ return;
1281
+ }
1282
+ if (res.status !== 200) {
1283
+ console.error("Val: could not get draft mode status", res);
1284
+ return;
1285
+ }
1286
+ setDraftMode(function (prev) {
1287
+ if (prev !== res.json.draftMode) {
1288
+ rerenderCounterRef.current++;
1289
+ window.dispatchEvent(new Event(VAL_EDIT_LANDED));
1290
+ return res.json.draftMode;
1291
+ }
1292
+ return prev;
1293
+ });
1294
+ })["catch"](function (err) {
1295
+ console.error("Val: could not get draft mode status", err);
1296
+ })["finally"](function () {
1297
+ if (pollDraftStatIdRef.current !== pollDraftStatId) {
1298
+ return;
1299
+ }
1300
+ pollDraftStatIdRef.current--;
1301
+ var handshaking = iframeSrc !== null;
1302
+ if (handshaking && Date.now() - startedAt > DRAFT_HANDSHAKE_TIMEOUT_MS) {
1303
+ /**
1304
+ * The handshake did not complete, so stop hammering.
1305
+ *
1306
+ * Clearing `iframeSrc` unmounts the hidden frame and takes the poll
1307
+ * back to its idle interval — which is the honest state: nothing is
1308
+ * in progress any more. Leaving it set is what made this
1309
+ * unrecoverable, because the ONLY other thing that clears it is the
1310
+ * `val-ready` message the frame never sent.
1311
+ */
1312
+ console.warn("Val: draft mode did not confirm in time. Falling back to the " + "idle poll — try toggling preview again.");
1313
+ setIframeSrc(null);
1314
+ return;
1315
+ }
1316
+ timeout = setTimeout(pollCurrentDraftMode, handshaking ? DRAFT_HANDSHAKE_POLL_MS : DRAFT_IDLE_POLL_MS);
1317
+ });
1318
+ }
1319
+ /**
1320
+ * When this spell of polling began.
1321
+ *
1322
+ * The effect re-runs when `iframeSrc` changes, so for the fast phase this is
1323
+ * the moment the handshake started — which is what the deadline below is
1324
+ * measured from.
1325
+ */
1326
+ var startedAt = Date.now();
1327
+ pollCurrentDraftMode();
1328
+ return function () {
1329
+ clearTimeout(timeout);
1330
+ };
1331
+ }, [mountOverlay, iframeSrc]);
1332
+ React.useEffect(function () {
1333
+ if (!mountOverlay) {
1334
+ return;
1335
+ }
1336
+ window.dispatchEvent(new CustomEvent("val-overlay-spa", {
1337
+ detail: {
1338
+ type: "draftMode",
1339
+ value: draftMode !== null && draftMode !== void 0 ? draftMode : false
1340
+ }
1341
+ }));
1342
+ }, [mountOverlay, draftMode, spaReady]);
1343
+ React.useEffect(function () {
1344
+ if (!mountOverlay) {
1345
+ SET_AUTO_TAG_JSX_ENABLED(false);
1346
+ } else {
1347
+ if (draftMode) {
1348
+ SET_AUTO_TAG_JSX_ENABLED(true);
1349
+ var reactServerComponentRefreshListener = function reactServerComponentRefreshListener(event) {
1350
+ if (event instanceof CustomEvent) {
1351
+ var _event$detail, _event$detail2;
1352
+ if (((_event$detail = event.detail) === null || _event$detail === void 0 ? void 0 : _event$detail.type) === "sources-synced") {
1353
+ setDraftSourcesSynced(true);
1354
+ return;
1355
+ }
1356
+ if (((_event$detail2 = event.detail) === null || _event$detail2 === void 0 ? void 0 : _event$detail2.type) === "source-update") {
1357
+ var _event$detail3, _event$detail4;
1358
+ var moduleFilePath = (_event$detail3 = event.detail) === null || _event$detail3 === void 0 ? void 0 : _event$detail3.moduleFilePath;
1359
+ var source = (_event$detail4 = event.detail) === null || _event$detail4 === void 0 ? void 0 : _event$detail4.source;
1360
+ if (typeof moduleFilePath === "string" && source !== undefined) {
1361
+ var changed = valStore.update(moduleFilePath, source);
1362
+ /**
1363
+ * Armed by a CHANGE, not by a message.
1364
+ *
1365
+ * The editor re-sends everything it holds whenever this page
1366
+ * becomes a new document — a reload, or the one the dev server does
1367
+ * when a publish rewrites the `.val.ts` files — and most of that
1368
+ * snapshot is content this page already has. Counting those
1369
+ * bought a whole-route request per publish whether or not the
1370
+ * page was out of date, which with auto-save on is one per pause
1371
+ * in typing.
1372
+ *
1373
+ * The catch-up that MATTERS still refreshes: a page whose server
1374
+ * render predates the write has no value for that module, so the
1375
+ * first arrival counts as a change.
1376
+ */
1377
+ if (changed && !props.disableRefresh) {
1378
+ rerenderCounterRef.current++;
1379
+ window.dispatchEvent(new Event(VAL_EDIT_LANDED));
1380
+ }
1381
+ } else {
1382
+ console.error("Val: invalid event detail", event.detail);
1383
+ }
1384
+ } else {
1385
+ console.error("Val: invalid custom event details (val-event)", event.detail);
1386
+ }
1387
+ } else {
1388
+ console.error("Val: invalid custom event (val-event)", event);
1389
+ }
1390
+ };
1391
+ window.addEventListener("val-event", reactServerComponentRefreshListener);
1392
+ return function () {
1393
+ window.removeEventListener("val-event", reactServerComponentRefreshListener);
1394
+ };
1395
+ }
1396
+ }
1397
+ }, [mountOverlay, draftMode, props.disableRefresh]);
1398
+ React.useEffect(function () {
1399
+ if (!mountOverlay) {
1400
+ return;
1401
+ }
1402
+ var listener = function listener(event) {
1403
+ if (event.origin === location.origin && event.data.type === "val-ready") {
1404
+ setIframeSrc(null);
1405
+ }
1406
+ };
1407
+ window.addEventListener("message", listener);
1408
+ return function () {
1409
+ window.removeEventListener("message", listener);
1410
+ };
1411
+ }, [mountOverlay]);
1412
+ var container = React.useRef(null);
1413
+ React.useEffect(function () {
1414
+ var _container$current;
1415
+ if (((_container$current = container.current) === null || _container$current === void 0 ? void 0 : _container$current.childElementCount) === 0) {
1416
+ window.dispatchEvent(new CustomEvent("val-append-overlay"));
1417
+ }
1418
+ });
1419
+ var _React$useState13 = React.useState(null),
1420
+ _React$useState14 = _slicedToArray(_React$useState13, 2),
1421
+ dropZone = _React$useState14[0],
1422
+ setDropZone = _React$useState14[1];
1423
+ React.useEffect(function () {
1424
+ var storedDropZone = localStorage.getItem("val-menu-drop-zone-default");
1425
+ if (storedDropZone) {
1426
+ setDropZone(storedDropZone);
1427
+ } else {
1428
+ setDropZone("val-menu-right-center");
1429
+ }
1430
+ }, []);
1431
+ useConfigStorageSave(props.config);
1432
+ var initTheme = React.useMemo(function () {
1433
+ return initSessionTheme(props.config);
1434
+ }, [props.config]);
1435
+ var _React$useState15 = React.useState(false),
1436
+ _React$useState16 = _slicedToArray(_React$useState15, 2),
1437
+ spaLoaded = _React$useState16[0],
1438
+ setSpaLoaded = _React$useState16[1];
1439
+ React.useEffect(function () {
1440
+ var listener = function listener() {
1441
+ setSpaLoaded(true);
1442
+ };
1443
+ window.addEventListener("val-ui-created", listener);
1444
+ return function () {
1445
+ window.removeEventListener("val-ui-created", listener);
1446
+ };
1447
+ }, []);
1448
+ // The pill is Val's chrome floating over the customer's page, so it takes
1449
+ // the float background rather than the studio's canvas.
1450
+ var _React$useMemo = React.useMemo(function () {
1451
+ if (initTheme !== "light") {
1452
+ return [floatDarkBg, "white"];
1453
+ }
1454
+ return [floatLightBg, "black"];
1455
+ }, [initTheme]),
1456
+ _React$useMemo2 = _slicedToArray(_React$useMemo, 2),
1457
+ backgroundColor = _React$useMemo2[0],
1458
+ textColor = _React$useMemo2[1];
1459
+ var commonStyles = React.useMemo(function () {
1460
+ return {
1461
+ "backdrop-blur": "backdrop-filter: blur(10px);",
1462
+ "text-text-primary": "color: ".concat(textColor, ";"),
1463
+ "bg-bg-primary": "background: ".concat(backgroundColor, ";"),
1464
+ rounded: "border-radius: 0.25rem;",
1465
+ fixed: "position: fixed;",
1466
+ "bottom-4": "bottom: 1rem;",
1467
+ "right-12": "right: 3rem;",
1468
+ "right-16": "right: 4rem;",
1469
+ "p-4": "padding: 1rem;",
1470
+ "p-2": "padding: 0.5rem;",
1471
+ "p-1": "padding: 0.25rem;",
1472
+ flex: "display: flex;",
1473
+ "items-center": "align-items: center;",
1474
+ "justify-center": "justify-content: center;"
1475
+ };
1476
+ }, [valPrefixedClass]);
1477
+ return /*#__PURE__*/jsxs(ValOverlayProvider, {
1478
+ draftMode: draftMode,
1479
+ draftModeReady: (_draftModeReady$curre2 = draftModeReady.current) === null || _draftModeReady$curre2 === void 0 ? void 0 : _draftModeReady$curre2.promise,
1480
+ draftSourcesSynced: draftSourcesSynced,
1481
+ suspend: suspendActive,
1482
+ store: valStore,
1483
+ children: [props.children, dropZone !== null && !spaLoaded && mountOverlay &&
1484
+ // The canvas frame loads no SPA, so its pill would spin for ever.
1485
+ !isCanvas && initTheme !== null && /*#__PURE__*/jsxs(React.Fragment, {
1486
+ children: [/*#__PURE__*/jsx("style", {
1487
+ 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")
1488
+ }), /*#__PURE__*/jsx("div", {
1489
+ className: "".concat(getPositionClassName(dropZone), " ").concat(cn(["p-4"])),
1490
+ children: /*#__PURE__*/jsx("div", {
1491
+ className: "".concat(cn(["flex", "justify-center", "items-center", "p-2"]), " ") + "".concat(cn(["text-text-primary", "bg-bg-primary", "rounded", "backdrop-blur"])),
1492
+ children: /*#__PURE__*/jsxs("svg", {
1493
+ xmlns: "http://www.w3.org/2000/svg",
1494
+ width: "16",
1495
+ height: "16",
1496
+ viewBox: "0 0 24 24",
1497
+ fill: "none",
1498
+ stroke: "currentColor",
1499
+ strokeWidth: "2",
1500
+ strokeLinecap: "round",
1501
+ strokeLinejoin: "round",
1502
+ children: [/*#__PURE__*/jsx("circle", {
1503
+ cx: "12",
1504
+ cy: "12",
1505
+ r: "11"
1506
+ }), /*#__PURE__*/jsx("line", {
1507
+ x1: "12",
1508
+ y1: "4",
1509
+ x2: "12",
1510
+ y2: "12",
1511
+ style: {
1512
+ transformOrigin: "center",
1513
+ animation: "rotate-clock 1000ms linear infinite"
1514
+ }
1515
+ }), /*#__PURE__*/jsx("line", {
1516
+ x1: "12",
1517
+ y1: "8",
1518
+ x2: "12",
1519
+ y2: "12",
1520
+ style: {
1521
+ transformOrigin: "center",
1522
+ animation: "rotate-clock 12000ms linear infinite"
1523
+ }
1524
+ })]
1525
+ })
1526
+ })
1527
+ })]
1528
+ }), mountOverlay && !isCanvas && /*#__PURE__*/jsxs(React.Fragment, {
1529
+ children: [/*#__PURE__*/jsx(ValScript, {
1530
+ src: "".concat(route, "/static").concat(VERSION ? "/".concat(VERSION) : "").concat(VAL_APP_PATH)
1531
+ }), /*#__PURE__*/jsx("div", {
1532
+ id: VAL_OVERLAY_ID,
1533
+ ref: container
1534
+ })]
1535
+ }), mountOverlay && isCanvas && /*#__PURE__*/jsx(ValCanvasBridge, {
1536
+ draftMode: draftMode === true,
1537
+ isRefreshing: isRefreshing
1538
+ }), mountOverlay && iframeSrc && /*#__PURE__*/jsx("iframe", {
1539
+ loading: "eager",
1540
+ style: {
1541
+ top: 10,
1542
+ left: 10,
1543
+ position: "absolute",
1544
+ width: 1000,
1545
+ height: 1000,
1546
+ visibility: "hidden"
1547
+ },
1548
+ src: iframeSrc
1549
+ }, iframeSrc)]
1550
+ });
1551
+ };
1552
+ function useConsoleLogEnableVal(showOverlay) {
1553
+ React.useEffect(function () {
1554
+ if (process.env["NODE_ENV"] === "development" && showOverlay === false && !isValStudioPath(location.pathname)) {
1555
+ console.warn("\n###########\n###########\n########### @@@@\n########### @@\n########### @@ @@ @@@@@@ @ @@\n########### @@ @@ @@ @@ @@\n########### @@ @@ %@ @ @@\n#### ##### @@ @@ .@ .@ @@\n### #### @@@@ @@: @@@. @@\n#### ##### @@@@ @@@@ =@@@@@@@@@\n###########\n\nThis page is built with Val Build - the lightweight CMS where content is code.\n\nVal is currently hidden.\n\nTo show Val, go to the following URL:\n".concat(window.location.origin, "/api/val/enable?redirect_to=").concat(encodeURIComponent(window.location.href), "\n \nYou are seeing this message because you are in development mode."));
1556
+ }
1557
+ }, [showOverlay]);
1558
+ }
1559
+ var positionStyles = prefixStyles({
1560
+ "left-0": "left: 0;",
1561
+ "top-0": "top: 0;",
1562
+ "left-1/2": "left: 50%;",
1563
+ "top-1/2": "top: 50%;",
1564
+ "-translate-y-1/2": "transform: translateY(-50%);",
1565
+ "-translate-x-1/2": "transform: translateX(-50%);",
1566
+ "right-0": "right: 0;",
1567
+ "bottom-0": "bottom: 0;"
1568
+ });
1569
+
1570
+ // This is a copy of the function from the ValMenu component.
1571
+ function getPositionClassName(dropZone) {
1572
+ var className = cn(["fixed", "transform"]);
1573
+ if (dropZone === "val-menu-left-top") {
1574
+ className += " ".concat(cn(["left-0", "top-0"]));
1575
+ } else if (dropZone === "val-menu-left-center") {
1576
+ className += " ".concat(cn(["left-0", "top-1/2", "-translate-y-1/2"]));
1577
+ } else if (dropZone === "val-menu-left-bottom") {
1578
+ className += " ".concat(cn(["left-0", "bottom-0"]));
1579
+ } else if (dropZone === "val-menu-center-top") {
1580
+ className += " ".concat(cn(["left-1/2", "-translate-x-1/2", "top-0"]));
1581
+ } else if (dropZone === "val-menu-center-bottom") {
1582
+ className += " ".concat(cn(["left-1/2", "-translate-x-1/2", "bottom-0"]));
1583
+ } else if (dropZone === "val-menu-right-top") {
1584
+ className += " ".concat(cn(["right-0", "top-0"]));
1585
+ } else if (dropZone === "val-menu-right-center") {
1586
+ className += " ".concat(cn(["right-0", "top-1/2", "-translate-y-1/2"]));
1587
+ } else if (dropZone === "val-menu-right-bottom") {
1588
+ className += " ".concat(cn(["right-0", "bottom-0"]));
1589
+ } else {
1590
+ className += " ".concat(cn(["right-0", "bottom-0"]));
1591
+ }
1592
+ return className;
1593
+ }
1594
+ function isValStudioPath(pathname) {
1595
+ return pathname.startsWith("/val");
1596
+ }
1597
+
1598
+ // function ValIcon() {
1599
+ // return (
1600
+ // <svg
1601
+ // width="32"
1602
+ // height="32"
1603
+ // viewBox="0 0 105 149"
1604
+ // fill="none"
1605
+ // xmlns="http://www.w3.org/2000/svg"
1606
+ // >
1607
+ // <g filter="url(#filter0_d_14_634)">
1608
+ // <path
1609
+ // d="M21.4768 23.3474C21.4768 22.4628 22.1939 21.7457 23.0785 21.7457H77.1357C78.0203 21.7457 78.7374 22.4628 78.7374 23.3474V125.055C78.7374 125.94 78.0203 126.657 77.1357 126.657H23.0785C22.1939 126.657 21.4768 125.94 21.4768 125.055V23.3474Z"
1610
+ // fill="#38CD98"
1611
+ // />
1612
+ // </g>
1613
+ // <g filter="url(#filter1_i_14_634)">
1614
+ // <circle cx="49.9068" cy="104.233" r="9.61017" fill="#1E1F2A" />
1615
+ // </g>
1616
+ // <defs>
1617
+ // <filter
1618
+ // id="filter0_d_14_634"
1619
+ // x="0.0397091"
1620
+ // y="0.30863"
1621
+ // width="100.135"
1622
+ // height="147.785"
1623
+ // filterUnits="userSpaceOnUse"
1624
+ // colorInterpolationFilters="sRGB"
1625
+ // >
1626
+ // <feFlood floodOpacity="0" result="BackgroundImageFix" />
1627
+ // <feColorMatrix
1628
+ // in="SourceAlpha"
1629
+ // type="matrix"
1630
+ // values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
1631
+ // result="hardAlpha"
1632
+ // />
1633
+ // <feOffset />
1634
+ // <feGaussianBlur stdDeviation="10.7185" />
1635
+ // <feComposite in2="hardAlpha" operator="out" />
1636
+ // <feColorMatrix
1637
+ // type="matrix"
1638
+ // values="0 0 0 0 0.219608 0 0 0 0 0.803922 0 0 0 0 0.501961 0 0 0 0.3 0"
1639
+ // />
1640
+ // <feBlend
1641
+ // mode="normal"
1642
+ // in2="BackgroundImageFix"
1643
+ // result="effect1_dropShadow_14_634"
1644
+ // />
1645
+ // <feBlend
1646
+ // mode="normal"
1647
+ // in="SourceGraphic"
1648
+ // in2="effect1_dropShadow_14_634"
1649
+ // result="shape"
1650
+ // />
1651
+ // </filter>
1652
+ // <filter
1653
+ // id="filter1_i_14_634"
1654
+ // x="40.2966"
1655
+ // y="94.6229"
1656
+ // width="19.2205"
1657
+ // height="19.2204"
1658
+ // filterUnits="userSpaceOnUse"
1659
+ // colorInterpolationFilters="sRGB"
1660
+ // >
1661
+ // <feFlood floodOpacity="0" result="BackgroundImageFix" />
1662
+ // <feBlend
1663
+ // mode="normal"
1664
+ // in="SourceGraphic"
1665
+ // in2="BackgroundImageFix"
1666
+ // result="shape"
1667
+ // />
1668
+ // <feColorMatrix
1669
+ // in="SourceAlpha"
1670
+ // type="matrix"
1671
+ // values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
1672
+ // result="hardAlpha"
1673
+ // />
1674
+ // <feOffset />
1675
+ // <feGaussianBlur stdDeviation="2.40254" />
1676
+ // <feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
1677
+ // <feColorMatrix
1678
+ // type="matrix"
1679
+ // values="0 0 0 0 0.219608 0 0 0 0 0.803922 0 0 0 0 0.501961 0 0 0 0.3 0"
1680
+ // />
1681
+ // <feBlend
1682
+ // mode="normal"
1683
+ // in2="shape"
1684
+ // result="effect1_innerShadow_14_634"
1685
+ // />
1686
+ // </filter>
1687
+ // </defs>
1688
+ // </svg>
1689
+ // );
1690
+ // }
1691
+
1692
+ var ValProvider = ValTanStackProvider;
1693
+
1694
+ function _objectWithoutPropertiesLoose(r, e) {
1695
+ if (null == r) return {};
1696
+ var t = {};
1697
+ for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
1698
+ if (-1 !== e.indexOf(n)) continue;
1699
+ t[n] = r[n];
1700
+ }
1701
+ return t;
1702
+ }
1703
+
1704
+ function _objectWithoutProperties(e, t) {
1705
+ if (null == e) return {};
1706
+ var o,
1707
+ r,
1708
+ i = _objectWithoutPropertiesLoose(e, t);
1709
+ if (Object.getOwnPropertySymbols) {
1710
+ var n = Object.getOwnPropertySymbols(e);
1711
+ for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]);
1712
+ }
1713
+ return i;
1714
+ }
1715
+
1716
+ var _excluded = ["src", "alt", "style", "width", "disableHotspot", "height"];
1717
+ function ValImage(props) {
1718
+ var src = props.src,
1719
+ alt = props.alt,
1720
+ style = props.style,
1721
+ width = props.width,
1722
+ disableHotspot = props.disableHotspot,
1723
+ height = props.height,
1724
+ rest = _objectWithoutProperties(props, _excluded);
1725
+ var valPathsOfUrl = src !== null && src !== void 0 && src.url ? decodeValPathsOfString(src.url) : undefined;
1726
+ var valPaths = valPathsOfUrl ? valPathsOfUrl : [];
1727
+ var maybeValPathOfAlt = alt ? decodeValPathsOfString(alt) : undefined;
1728
+ if (maybeValPathOfAlt) {
1729
+ var _iterator = _createForOfIteratorHelper(maybeValPathOfAlt),
1730
+ _step;
1731
+ try {
1732
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
1733
+ var valPath = _step.value;
1734
+ valPaths.push(valPath);
1735
+ }
1736
+ } catch (err) {
1737
+ _iterator.e(err);
1738
+ } finally {
1739
+ _iterator.f();
1740
+ }
1741
+ }
1742
+ var hotspot = src === null || src === void 0 ? void 0 : src.hotspot;
1743
+ var imageStyle = hotspot && !disableHotspot ? _objectSpread2(_objectSpread2({}, style), {}, {
1744
+ objectPosition: "".concat(hotspot.x * 100, "% ").concat(hotspot.y * 100, "%")
1745
+ }) : style;
1746
+ // The authored dimensions are only used when the caller has not said
1747
+ // otherwise: an explicit width/height is the caller sizing the element, and
1748
+ // overriding it with the file's own size would fight them.
1749
+ var preferMetadataDims = ((src === null || src === void 0 ? void 0 : src.width) !== undefined || (src === null || src === void 0 ? void 0 : src.height) !== undefined) && !width && !height;
1750
+ return /*#__PURE__*/jsx("img", _objectSpread2(_objectSpread2({}, rest), {}, {
1751
+ src: valPathsOfUrl && valPathsOfUrl.length > 0 ? raw(src === null || src === void 0 ? void 0 : src.url) : src === null || src === void 0 ? void 0 : src.url,
1752
+ "data-val-path": valPaths.join(","),
1753
+ "data-val-attr-alt": maybeValPathOfAlt,
1754
+ "data-val-attr-src": valPathsOfUrl && valPathsOfUrl.length > 0 ? valPathsOfUrl.join(",") : undefined,
1755
+ style: imageStyle,
1756
+ alt: alt ? raw(alt) : src !== null && src !== void 0 && src.alt ? raw(src === null || src === void 0 ? void 0 : src.alt) : "",
1757
+ width: preferMetadataDims ? src === null || src === void 0 ? void 0 : src.width : width,
1758
+ height: preferMetadataDims ? src === null || src === void 0 ? void 0 : src.height : height
1759
+ }));
1760
+ }
1761
+
1762
+ var VAL_READY_PING_MS = 100;
1763
+ var ValApp = function ValApp(_ref) {
1764
+ var config = _ref.config,
1765
+ children = _ref.children;
1766
+ var route = "/api/val"; // TODO: make configurable
1767
+ var _useState = useState(),
1768
+ _useState2 = _slicedToArray(_useState, 2),
1769
+ inMessageMode = _useState2[0],
1770
+ setInMessageMode = _useState2[1];
1771
+ var isClientSIde = inMessageMode === undefined;
1772
+ useConfigStorageSave(config);
1773
+ var container = useRef(null);
1774
+ useEffect(function () {
1775
+ if (location.search === "?message_onready=true") {
1776
+ setInMessageMode(true);
1777
+ var interval = setInterval(function () {
1778
+ window.parent.postMessage({
1779
+ type: "val-ready"
1780
+ }, "*");
1781
+ }, VAL_READY_PING_MS);
1782
+ return function () {
1783
+ clearInterval(interval);
1784
+ };
1785
+ } else {
1786
+ setInMessageMode(false);
1787
+ }
1788
+ }, []);
1789
+ useEffect(function () {
1790
+ var _container$current;
1791
+ if (((_container$current = container.current) === null || _container$current === void 0 ? void 0 : _container$current.childElementCount) === 0) {
1792
+ window.dispatchEvent(new CustomEvent("val-append-studio"));
1793
+ }
1794
+ });
1795
+
1796
+ // this theme is used to avoid flickering
1797
+ var _useState3 = useState(config.defaultTheme || null),
1798
+ _useState4 = _slicedToArray(_useState3, 2),
1799
+ loadingTheme = _useState4[0],
1800
+ setLoadingTheme = _useState4[1];
1801
+ useEffect(function () {
1802
+ var theme = localStorage.getItem("val-theme-" + ((config === null || config === void 0 ? void 0 : config.project) || "unknown"));
1803
+ if (theme === "dark") {
1804
+ setLoadingTheme("dark");
1805
+ } else if (theme === "light") {
1806
+ setLoadingTheme("light");
1807
+ } else if (config.defaultTheme) {
1808
+ setLoadingTheme(config.defaultTheme);
1809
+ }
1810
+ }, [config]);
1811
+ // The studio's canvas, so the loading screen is the same colour as what
1812
+ // replaces it rather than a flash of a different one.
1813
+ var darkBg = canvasDarkBg;
1814
+ var lightBg = canvasLightBg;
1815
+ useEffect(function () {
1816
+ if (inMessageMode || loadingTheme === null) {
1817
+ return;
1818
+ }
1819
+ var body = document.body;
1820
+ var prevBodyBg = body.style.backgroundColor;
1821
+ var prevBodyMinHeight = body.style.minHeight;
1822
+ var prevBodyMinWidth = body.style.minWidth;
1823
+ body.style.backgroundColor = loadingTheme === "dark" ? darkBg : lightBg;
1824
+ body.style.minHeight = "100vh";
1825
+ body.style.minWidth = "100%";
1826
+ // A stable reference: `removeEventListener` compares by identity, so a
1827
+ // second arrow function removes nothing and every re-run of this effect
1828
+ // used to leave another listener behind.
1829
+ var onCssLoaded = function onCssLoaded() {
1830
+ // css was loaded, has been loaded, so let app decide what to do
1831
+ setLoadingTheme(null);
1832
+ };
1833
+ window.addEventListener("val-css-loaded", onCssLoaded);
1834
+ return function () {
1835
+ body.style.backgroundColor = prevBodyBg;
1836
+ body.style.minHeight = prevBodyMinHeight;
1837
+ body.style.minWidth = prevBodyMinWidth;
1838
+ window.removeEventListener("val-css-loaded", onCssLoaded);
1839
+ };
1840
+ }, [inMessageMode, loadingTheme]);
1841
+ if (loadingTheme !== null && isClientSIde) {
1842
+ return /*#__PURE__*/jsxs("div", {
1843
+ style: {
1844
+ color: loadingTheme === "dark" ? "white" : "black",
1845
+ display: "flex",
1846
+ justifyContent: "center",
1847
+ alignItems: "center",
1848
+ padding: "1rem",
1849
+ backgroundColor: loadingTheme === "dark" ? darkBg : lightBg,
1850
+ minHeight: "100vh",
1851
+ minWidth: "100%"
1852
+ },
1853
+ children: [/*#__PURE__*/jsx("style", {
1854
+ children: ".".concat(valPrefixedClass, "animate-spin {\n animation: ").concat(valPrefixedClass, "spin 2s linear infinite;\n}\n@keyframes ").concat(valPrefixedClass, "spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}")
1855
+ }), /*#__PURE__*/jsx("svg", {
1856
+ xmlns: "http://www.w3.org/2000/svg",
1857
+ width: "24",
1858
+ height: "24",
1859
+ viewBox: "0 0 24 24",
1860
+ fill: "none",
1861
+ stroke: "currentColor",
1862
+ strokeWidth: "2",
1863
+ strokeLinecap: "round",
1864
+ strokeLinejoin: "round",
1865
+ className: cn(["animate-spin"]),
1866
+ children: /*#__PURE__*/jsx("path", {
1867
+ d: "M21 12a9 9 0 1 1-6.219-8.56"
1868
+ })
1869
+ })]
1870
+ });
1871
+ }
1872
+ if (inMessageMode) {
1873
+ return /*#__PURE__*/jsx("div", {
1874
+ children: "Val Studio is disabled: in message mode"
1875
+ });
1876
+ }
1877
+ return /*#__PURE__*/jsxs(Fragment, {
1878
+ children: [/*#__PURE__*/jsx(ValScript, {
1879
+ src: "".concat(route, "/static").concat(VERSION ? "/".concat(VERSION) : "").concat(VAL_APP_PATH)
1880
+ }), /*#__PURE__*/jsx("div", {
1881
+ id: VAL_APP_ID,
1882
+ ref: container
1883
+ }), children]
1884
+ });
1885
+ };
1886
+
1887
+ // Keep in sync with VAL_MODULES_UPDATED_EVENT in @valbuild/ui (useValModules.ts).
1888
+ var VAL_MODULES_UPDATED_EVENT = "val-modules-updated";
1889
+
1890
+ /**
1891
+ * Registers the user's `val.modules` registry on `window.__VAL_MODULES__` so
1892
+ * the Val editor SPA (both the `/val` app and the on-page overlay) can read it.
1893
+ *
1894
+ * Must be called from a component that statically imports `val.modules`: the
1895
+ * module `def` entries are function closures, so the registry cannot be
1896
+ * serialized through a loader and has to be pulled into the client bundle and
1897
+ * registered via the window global instead.
1898
+ *
1899
+ * Re-runs when the `modules` reference changes (e.g. on HMR) and dispatches
1900
+ * `val-modules-updated` so the SPA picks up the new registry.
1901
+ */
1902
+ function useRegisterValModules(modules) {
1903
+ useEffect(function () {
1904
+ window.__VAL_MODULES__ = modules;
1905
+ window.dispatchEvent(new CustomEvent(VAL_MODULES_UPDATED_EVENT));
1906
+ }, [modules]);
1907
+ }
1908
+
1909
+ /**
1910
+ * Registers the user's `val.modules` registry for the Val editor SPA.
1911
+ *
1912
+ * Render this inside both `<ValProvider>` (the root route) and `<ValApp>` (the
1913
+ * `/val` route), passing your `val.modules` default export:
1914
+ *
1915
+ * @example
1916
+ * import { ValModulesClient } from "@valbuild/tanstack";
1917
+ * import valModules from "../val.modules";
1918
+ *
1919
+ * <ValModulesClient modules={valModules} />
1920
+ */
1921
+ function ValModulesClient(_ref) {
1922
+ var modules = _ref.modules;
1923
+ useRegisterValModules(modules);
1924
+ return null;
1925
+ }
1926
+
1927
+ // NOTE! Side effects:
1928
+ autoTagJSX();
1929
+
1930
+ // Convenience types
1931
+
1932
+ /**
1933
+ * Annotated rather than inferred.
1934
+ *
1935
+ * The inferred type of the spread below reaches into @valbuild/core's internal
1936
+ * declaration files for names like `NonEmptyArray` and `createValPathOfItem`,
1937
+ * which the declaration emitter cannot write down portably: `preconstruct
1938
+ * build` fails with a wall of TS2883 "the inferred type of 'Internal' cannot be
1939
+ * named without a reference to ..." while `tsc --noEmit` stays happy, so it only
1940
+ * shows up in the build job. Naming the type in terms of `typeof InternalCore`
1941
+ * keeps everything the emitter needs reachable through the package entry point.
1942
+ */
1943
+
1944
+ var Internal = _objectSpread2(_objectSpread2({}, Internal$1), {}, {
1945
+ VERSION: _objectSpread2(_objectSpread2({}, Internal$1.VERSION), {}, {
1946
+ tanstack: VERSION$1
1947
+ })
1948
+ });
1949
+
1950
+ export { Internal, ValApp, ValImage, ValModulesClient, ValProvider, initVal, useRegisterValModules };