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