@valbuild/shared 0.150.8 → 0.150.11
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.
- package/CHANGELOG.md +30 -0
- package/client/dist/valbuild-shared-client.cjs.dev.js +16 -2
- package/client/dist/valbuild-shared-client.cjs.prod.js +16 -2
- package/client/dist/valbuild-shared-client.esm.js +2 -2
- package/dist/{acquireTypes-dd976839.cjs.prod.js → acquireTypes-6250a305.cjs.prod.js} +46 -46
- package/dist/{acquireTypes-6e2f7961.esm.js → acquireTypes-c2227b23.esm.js} +2 -2
- package/dist/{acquireTypes-84a4077a.cjs.dev.js → acquireTypes-c9e757a1.cjs.dev.js} +46 -46
- package/dist/{slicedToArray-22567815.esm.js → createForOfIteratorHelper-9b80f1a2.esm.js} +136 -106
- package/dist/{slicedToArray-d99b4e2e.cjs.prod.js → createForOfIteratorHelper-9ca2078d.cjs.prod.js} +142 -111
- package/dist/{slicedToArray-c8a29f6b.cjs.dev.js → createForOfIteratorHelper-f2d7ac6d.cjs.dev.js} +142 -111
- package/dist/declarations/src/client/index.d.ts +2 -0
- package/dist/declarations/src/internal/ApiRoutes.d.ts +23 -0
- package/dist/declarations/src/internal/index.d.ts +2 -0
- package/dist/declarations/src/internal/pageInspection.d.ts +117 -0
- package/dist/declarations/src/internal/quickPreview.d.ts +91 -0
- package/dist/declarations/src/internal/valCanvasProtocol.d.ts +53 -0
- package/dist/declarations/src/publish/buildProposal.d.ts +16 -1
- package/dist/declarations/src/publish/buildQuickPreview.d.ts +46 -0
- package/dist/declarations/src/publish/index.d.ts +3 -2
- package/dist/declarations/src/publish/runStudioDeploy.d.ts +10 -0
- package/dist/getSitemapTree-5354ce90.cjs.prod.js +1421 -0
- package/dist/getSitemapTree-97b3166f.esm.js +1381 -0
- package/dist/getSitemapTree-d934ff9c.cjs.dev.js +1421 -0
- package/dist/{proposalProject-689c24cc.esm.js → proposalProject-5da177d5.esm.js} +1 -2
- package/dist/{proposalProject-926f2c68.cjs.dev.js → proposalProject-76caa377.cjs.prod.js} +2 -3
- package/dist/{proposalProject-fab0c026.cjs.prod.js → proposalProject-81fee08f.cjs.dev.js} +2 -3
- package/dist/{protocol-7ddc1a71.esm.js → protocol-55cfa3cf.esm.js} +1 -1
- package/dist/{protocol-4e2a455b.cjs.prod.js → protocol-b1055ef0.cjs.prod.js} +6 -6
- package/dist/{protocol-ac1c3ec7.cjs.dev.js → protocol-d0efb8cd.cjs.dev.js} +6 -6
- package/dist/wrapNativeSuper-639b12b7.esm.js +107 -0
- package/dist/wrapNativeSuper-8673414f.cjs.prod.js +113 -0
- package/dist/wrapNativeSuper-933f991e.cjs.dev.js +113 -0
- package/internal/dist/valbuild-shared-internal.cjs.dev.js +72 -34
- package/internal/dist/valbuild-shared-internal.cjs.prod.js +72 -34
- package/internal/dist/valbuild-shared-internal.esm.js +30 -7
- package/package.json +2 -2
- package/publish/dist/valbuild-shared-publish.cjs.dev.js +589 -237
- package/publish/dist/valbuild-shared-publish.cjs.prod.js +589 -237
- package/publish/dist/valbuild-shared-publish.esm.js +504 -155
- package/typecheck/dist/valbuild-shared-typecheck.cjs.dev.js +34 -34
- package/typecheck/dist/valbuild-shared-typecheck.cjs.prod.js +34 -34
- package/typecheck/dist/valbuild-shared-typecheck.esm.js +5 -5
- package/typecheck/testing/dist/valbuild-shared-typecheck-testing.cjs.dev.js +7 -7
- package/typecheck/testing/dist/valbuild-shared-typecheck-testing.cjs.prod.js +7 -7
- package/typecheck/testing/dist/valbuild-shared-typecheck-testing.esm.js +3 -3
- package/dist/createForOfIteratorHelper-666a6d17.esm.js +0 -137
- package/dist/createForOfIteratorHelper-87a33eff.cjs.dev.js +0 -145
- package/dist/createForOfIteratorHelper-fef4fa7d.cjs.prod.js +0 -145
- package/dist/getSitemapTree-873f7266.cjs.prod.js +0 -743
- package/dist/getSitemapTree-b6161fd8.esm.js +0 -717
- package/dist/getSitemapTree-be70d9fc.cjs.dev.js +0 -743
|
@@ -0,0 +1,1421 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var createForOfIteratorHelper = require('./createForOfIteratorHelper-9ca2078d.cjs.prod.js');
|
|
4
|
+
var core = require('@valbuild/core');
|
|
5
|
+
|
|
6
|
+
var VAL_CONFIG_SESSION_STORAGE_KEY = "val-config";
|
|
7
|
+
var VAL_THEME_SESSION_STORAGE_KEY = "val-theme";
|
|
8
|
+
var VAL_AI_SESSION_STORAGE_KEY = "val-ai-session";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A quick preview: a proposal's page, running the proposal's CLIENT code
|
|
12
|
+
* built in the design tab, without a build being uploaded or served
|
|
13
|
+
* (valbuild/home `docs/design-tab.md`).
|
|
14
|
+
*
|
|
15
|
+
* A full build of a proposal bundles the app for the server and the browser,
|
|
16
|
+
* uploads it, and has the platform render every page before it is served --
|
|
17
|
+
* minutes. Most of what the design tab changes is components and styles,
|
|
18
|
+
* which the browser half alone shows. So the tab bundles the app in its
|
|
19
|
+
* builder worker, and a service worker on the proposal's address hands the
|
|
20
|
+
* page that bundle instead of the served one:
|
|
21
|
+
*
|
|
22
|
+
* 1. The tab sends the files (`/_app/app.js`, its chunks, the stylesheet and
|
|
23
|
+
* assets) to the page in the preview, over the canvas protocol.
|
|
24
|
+
* 2. The page -- the bridge, which is the only thing at that origin the tab
|
|
25
|
+
* can talk to -- registers {@link QUICK_PREVIEW_SERVICE_WORKER_SOURCE}
|
|
26
|
+
* and stores the files in it (Cache Storage, so a worker the browser
|
|
27
|
+
* stopped still has them).
|
|
28
|
+
* 3. The tab opens the page with `?val_quick=<id>`. The worker serves that
|
|
29
|
+
* document's `/_app/` requests from the files, and everything else from
|
|
30
|
+
* the network: the HTML is the last build's, rendered by its server, and
|
|
31
|
+
* the new code takes it over in the browser.
|
|
32
|
+
*
|
|
33
|
+
* What it cannot show, and the tab says so: anything on the server. Server
|
|
34
|
+
* functions, API routes and loaders that run there are the last build's, as
|
|
35
|
+
* is the page's first render and its `head`. A new dependency is not there
|
|
36
|
+
* either. A build is still what decides, before Publish.
|
|
37
|
+
*
|
|
38
|
+
* Only a document opened with the parameter is served from the files, so a
|
|
39
|
+
* visitor to the proposal's address -- even in the same browser -- is never
|
|
40
|
+
* given them.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/** The parameter a quick preview's page is opened with. */
|
|
44
|
+
var QUICK_PREVIEW_PARAM = "val_quick";
|
|
45
|
+
|
|
46
|
+
/** Where Val's API serves the worker: under `/api/val`. */
|
|
47
|
+
var QUICK_PREVIEW_SERVICE_WORKER_PATH = "/quick-preview-sw.js";
|
|
48
|
+
|
|
49
|
+
/** What the page sends the worker to store a preview's files. */
|
|
50
|
+
var QUICK_PREVIEW_STORE_MESSAGE = "val-quick-preview-store";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A preview's files, by their path under `/_app/`: `app.js`, `c-<hash>.js`,
|
|
54
|
+
* `app.css`, `a/<hash>.png`. A binary file is base64.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The files of a quick preview: paths under `/_app/` with no way out of it,
|
|
59
|
+
* each a string body and a type.
|
|
60
|
+
*/
|
|
61
|
+
function isQuickPreviewFiles(value) {
|
|
62
|
+
if (createForOfIteratorHelper._typeof(value) !== "object" || value === null) return false;
|
|
63
|
+
return Object.entries(value).every(function (_ref) {
|
|
64
|
+
var _ref2 = createForOfIteratorHelper._slicedToArray(_ref, 2),
|
|
65
|
+
path = _ref2[0],
|
|
66
|
+
file = _ref2[1];
|
|
67
|
+
return /^[A-Za-z0-9_][A-Za-z0-9_./-]*$/.test(path) && !path.includes("..") && createForOfIteratorHelper._typeof(file) === "object" && file !== null && typeof file.body === "string" && typeof file.contentType === "string";
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The worker, as the source it is served as: plain, and small, because it is
|
|
73
|
+
* served as a file of its own (`demoServiceWorker.ts` is the same).
|
|
74
|
+
*/
|
|
75
|
+
var QUICK_PREVIEW_SERVICE_WORKER_SOURCE = "\n\"use strict\";\nconst PARAM = \"".concat(QUICK_PREVIEW_PARAM, "\";\nconst CACHE = \"val-quick-preview\";\nconst PREFIX = \"/__val_quick/\";\nconst INDEX = PREFIX + \"index\";\nconst KEEP = 4;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(self.clients.claim());\n});\n\nself.addEventListener(\"message\", (event) => {\n const data = event.data;\n if (!data || data.type !== \"").concat(QUICK_PREVIEW_STORE_MESSAGE, "\") return;\n const port = event.ports && event.ports[0];\n event.waitUntil(\n store(data.id, data.files).then(\n () => port && port.postMessage({ ok: true }),\n (error) =>\n port &&\n port.postMessage({\n ok: false,\n message: String((error && error.message) || error),\n }),\n ),\n );\n});\n\nfunction keyOf(id, path) {\n return PREFIX + id + \"/\" + path;\n}\n\nasync function store(id, files) {\n const cache = await caches.open(CACHE);\n for (const path of Object.keys(files)) {\n const file = files[path];\n const body = file.base64\n ? Uint8Array.from(atob(file.body), (c) => c.charCodeAt(0))\n : file.body;\n await cache.put(\n keyOf(id, path),\n new Response(body, {\n headers: { \"Content-Type\": file.contentType, \"Cache-Control\": \"no-store\" },\n }),\n );\n }\n // The newest few previews are kept; older ones are deleted.\n const index = await cache.match(INDEX);\n const ids = [id].concat(\n (index ? await index.json() : []).filter((other) => other !== id),\n );\n const dropped = ids.slice(KEEP);\n if (dropped.length > 0) {\n for (const request of await cache.keys()) {\n const path = new URL(request.url).pathname;\n if (dropped.some((old) => path.startsWith(PREFIX + old + \"/\"))) {\n await cache.delete(request);\n }\n }\n }\n await cache.put(INDEX, new Response(JSON.stringify(ids.slice(0, KEEP))));\n}\n\n/** Which preview a document is: by its id, or by the address it was opened at. */\nconst byClient = new Map();\nasync function previewOf(clientId) {\n if (!clientId) return null;\n if (byClient.has(clientId)) return byClient.get(clientId);\n const client = await self.clients.get(clientId);\n const id = client ? new URL(client.url).searchParams.get(PARAM) : null;\n byClient.set(clientId, id);\n return id;\n}\n\nself.addEventListener(\"fetch\", (event) => {\n const url = new URL(event.request.url);\n if (url.origin !== self.location.origin) return;\n if (event.request.mode === \"navigate\") {\n if (event.resultingClientId) {\n byClient.set(event.resultingClientId, url.searchParams.get(PARAM));\n }\n return;\n }\n if (!url.pathname.startsWith(\"/_app/\")) return;\n event.respondWith(serve(event, url));\n});\n\nasync function serve(event, url) {\n const id = await previewOf(event.clientId);\n if (id) {\n const hit = await caches.match(keyOf(id, url.pathname.slice(6)), {\n cacheName: CACHE,\n });\n if (hit) return hit;\n }\n return fetch(event.request);\n}\n");
|
|
76
|
+
|
|
77
|
+
/** How long the worker is given to install and store a preview. */
|
|
78
|
+
var STORE_TIMEOUT_MS = 30000;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Store a preview's files in the worker, registering it first: what the
|
|
82
|
+
* bridge does when the tab sends `quickPreview`. `swUrl` is the worker's
|
|
83
|
+
* address on this origin (`/api/val` + {@link QUICK_PREVIEW_SERVICE_WORKER_PATH}).
|
|
84
|
+
*/
|
|
85
|
+
function storeQuickPreview(_x, _x2, _x3) {
|
|
86
|
+
return _storeQuickPreview.apply(this, arguments);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The registration's worker, once it is active. */
|
|
90
|
+
function _storeQuickPreview() {
|
|
91
|
+
_storeQuickPreview = createForOfIteratorHelper._asyncToGenerator(/*#__PURE__*/createForOfIteratorHelper._regenerator().m(function _callee(id, files, options) {
|
|
92
|
+
var _options$container;
|
|
93
|
+
var container, registration, worker;
|
|
94
|
+
return createForOfIteratorHelper._regenerator().w(function (_context) {
|
|
95
|
+
while (1) switch (_context.n) {
|
|
96
|
+
case 0:
|
|
97
|
+
container = (_options$container = options.container) !== null && _options$container !== void 0 ? _options$container : typeof navigator !== "undefined" ? navigator.serviceWorker : undefined;
|
|
98
|
+
if (!(container === undefined)) {
|
|
99
|
+
_context.n = 1;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
throw new Error("This browser cannot show a quick preview here: it has no service workers on this address (a private window, or one that is not https).");
|
|
103
|
+
case 1:
|
|
104
|
+
_context.n = 2;
|
|
105
|
+
return container.register(options.swUrl, {
|
|
106
|
+
scope: "/"
|
|
107
|
+
});
|
|
108
|
+
case 2:
|
|
109
|
+
registration = _context.v;
|
|
110
|
+
_context.n = 3;
|
|
111
|
+
return activeWorker(registration);
|
|
112
|
+
case 3:
|
|
113
|
+
worker = _context.v;
|
|
114
|
+
_context.n = 4;
|
|
115
|
+
return new Promise(function (resolve, reject) {
|
|
116
|
+
var channel = new MessageChannel();
|
|
117
|
+
var timer = setTimeout(function () {
|
|
118
|
+
return reject(new Error("The quick preview's worker did not store the files."));
|
|
119
|
+
}, STORE_TIMEOUT_MS);
|
|
120
|
+
channel.port1.onmessage = function (event) {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
var answer = event.data;
|
|
123
|
+
if (createForOfIteratorHelper._typeof(answer) === "object" && answer !== null && "ok" in answer && answer.ok === true) {
|
|
124
|
+
resolve();
|
|
125
|
+
} else {
|
|
126
|
+
reject(new Error(createForOfIteratorHelper._typeof(answer) === "object" && answer !== null && "message" in answer && typeof answer.message === "string" ? answer.message : "The quick preview's worker could not store the files."));
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
worker.postMessage({
|
|
130
|
+
type: QUICK_PREVIEW_STORE_MESSAGE,
|
|
131
|
+
id: id,
|
|
132
|
+
files: files
|
|
133
|
+
}, [channel.port2]);
|
|
134
|
+
});
|
|
135
|
+
case 4:
|
|
136
|
+
return _context.a(2);
|
|
137
|
+
}
|
|
138
|
+
}, _callee);
|
|
139
|
+
}));
|
|
140
|
+
return _storeQuickPreview.apply(this, arguments);
|
|
141
|
+
}
|
|
142
|
+
function activeWorker(registration) {
|
|
143
|
+
var _registration$install;
|
|
144
|
+
var active = registration.active;
|
|
145
|
+
if (active !== null) return Promise.resolve(active);
|
|
146
|
+
var pending = (_registration$install = registration.installing) !== null && _registration$install !== void 0 ? _registration$install : registration.waiting;
|
|
147
|
+
if (pending === null) {
|
|
148
|
+
return Promise.reject(new Error("The quick preview's worker did not install."));
|
|
149
|
+
}
|
|
150
|
+
return new Promise(function (resolve, reject) {
|
|
151
|
+
var timer = setTimeout(function () {
|
|
152
|
+
return reject(new Error("The quick preview's worker did not start."));
|
|
153
|
+
}, STORE_TIMEOUT_MS);
|
|
154
|
+
pending.addEventListener("statechange", function () {
|
|
155
|
+
if (pending.state === "activated") {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
resolve(pending);
|
|
158
|
+
} else if (pending.state === "redundant") {
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
reject(new Error("The quick preview's worker was replaced."));
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** The address of a page as a quick preview `id`. */
|
|
167
|
+
function withQuickPreviewParam(url, id) {
|
|
168
|
+
var parsed = new URL(url);
|
|
169
|
+
parsed.searchParams.set(QUICK_PREVIEW_PARAM, id);
|
|
170
|
+
return parsed.toString();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The one origin allowed to send this page a quick preview: the site's, where
|
|
175
|
+
* the design tab is, as this page's own Val server says it (the proposals
|
|
176
|
+
* list's `siteUrl`). `null` when it does not say.
|
|
177
|
+
*
|
|
178
|
+
* A quick preview is code that runs on this address, so the bridge takes one
|
|
179
|
+
* only from its parent, and only when that parent is the site: a page that
|
|
180
|
+
* framed this one could otherwise post a script into the worker and open
|
|
181
|
+
* the frame on it.
|
|
182
|
+
*/
|
|
183
|
+
function quickPreviewSiteOrigin(_x4) {
|
|
184
|
+
return _quickPreviewSiteOrigin.apply(this, arguments);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The site's origin, as this page's server named it: asked once per page. */
|
|
188
|
+
function _quickPreviewSiteOrigin() {
|
|
189
|
+
_quickPreviewSiteOrigin = createForOfIteratorHelper._asyncToGenerator(/*#__PURE__*/createForOfIteratorHelper._regenerator().m(function _callee2(api) {
|
|
190
|
+
var fetchImpl,
|
|
191
|
+
res,
|
|
192
|
+
body,
|
|
193
|
+
siteUrl,
|
|
194
|
+
_args2 = arguments;
|
|
195
|
+
return createForOfIteratorHelper._regenerator().w(function (_context2) {
|
|
196
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
197
|
+
case 0:
|
|
198
|
+
fetchImpl = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : fetch;
|
|
199
|
+
_context2.n = 1;
|
|
200
|
+
return fetchImpl("".concat(api.replace(/\/+$/, ""), "/proposals-api"), {
|
|
201
|
+
credentials: "same-origin",
|
|
202
|
+
headers: {
|
|
203
|
+
Accept: "application/json"
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
case 1:
|
|
207
|
+
res = _context2.v;
|
|
208
|
+
if (res.ok) {
|
|
209
|
+
_context2.n = 2;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
return _context2.a(2, null);
|
|
213
|
+
case 2:
|
|
214
|
+
_context2.n = 3;
|
|
215
|
+
return res.json();
|
|
216
|
+
case 3:
|
|
217
|
+
body = _context2.v;
|
|
218
|
+
if (!(createForOfIteratorHelper._typeof(body) !== "object" || body === null || !("siteUrl" in body))) {
|
|
219
|
+
_context2.n = 4;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
return _context2.a(2, null);
|
|
223
|
+
case 4:
|
|
224
|
+
siteUrl = body.siteUrl;
|
|
225
|
+
if (!(typeof siteUrl !== "string")) {
|
|
226
|
+
_context2.n = 5;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
return _context2.a(2, null);
|
|
230
|
+
case 5:
|
|
231
|
+
_context2.p = 5;
|
|
232
|
+
return _context2.a(2, new URL(siteUrl).origin);
|
|
233
|
+
case 6:
|
|
234
|
+
_context2.p = 6;
|
|
235
|
+
_context2.v;
|
|
236
|
+
return _context2.a(2, null);
|
|
237
|
+
}
|
|
238
|
+
}, _callee2, null, [[5, 6]]);
|
|
239
|
+
}));
|
|
240
|
+
return _quickPreviewSiteOrigin.apply(this, arguments);
|
|
241
|
+
}
|
|
242
|
+
var siteOriginOnce = null;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Whether `origin` may ask this page for what only the design tab should
|
|
246
|
+
* have -- what is on it (`readPage`), or code to run (`quickPreview`): this
|
|
247
|
+
* page's own origin (the Studio's canvas), or the site's, where the design
|
|
248
|
+
* tab is ({@link quickPreviewSiteOrigin}). Asked of the server once, and
|
|
249
|
+
* again only while it has not said.
|
|
250
|
+
*/
|
|
251
|
+
function isDesignTabOrigin(_x5, _x6) {
|
|
252
|
+
return _isDesignTabOrigin.apply(this, arguments);
|
|
253
|
+
}
|
|
254
|
+
function _isDesignTabOrigin() {
|
|
255
|
+
_isDesignTabOrigin = createForOfIteratorHelper._asyncToGenerator(/*#__PURE__*/createForOfIteratorHelper._regenerator().m(function _callee3(origin, options) {
|
|
256
|
+
var site;
|
|
257
|
+
return createForOfIteratorHelper._regenerator().w(function (_context3) {
|
|
258
|
+
while (1) switch (_context3.n) {
|
|
259
|
+
case 0:
|
|
260
|
+
if (!(origin === options.own)) {
|
|
261
|
+
_context3.n = 1;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
return _context3.a(2, true);
|
|
265
|
+
case 1:
|
|
266
|
+
siteOriginOnce !== null && siteOriginOnce !== void 0 ? siteOriginOnce : siteOriginOnce = quickPreviewSiteOrigin(options.api, options.fetchImpl)["catch"](function () {
|
|
267
|
+
return null;
|
|
268
|
+
});
|
|
269
|
+
_context3.n = 2;
|
|
270
|
+
return siteOriginOnce;
|
|
271
|
+
case 2:
|
|
272
|
+
site = _context3.v;
|
|
273
|
+
if (site === null) siteOriginOnce = null;
|
|
274
|
+
return _context3.a(2, site !== null && site === origin);
|
|
275
|
+
}
|
|
276
|
+
}, _callee3);
|
|
277
|
+
}));
|
|
278
|
+
return _isDesignTabOrigin.apply(this, arguments);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Marks a page load as the studio's canvas frame. */
|
|
282
|
+
var VAL_CANVAS_PARAM = "val_canvas";
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Every message is tagged with this, and the tag is checked on receipt.
|
|
286
|
+
*
|
|
287
|
+
* A window receives messages from anything that has a handle on it —
|
|
288
|
+
* extensions, embeds, other frames — and `event.data` is whatever they chose to
|
|
289
|
+
* send. Checking a discriminator before reading anything else is what keeps a
|
|
290
|
+
* stray message from being read as a canvas instruction.
|
|
291
|
+
*/
|
|
292
|
+
var VAL_CANVAS_MESSAGE = "val-canvas";
|
|
293
|
+
|
|
294
|
+
/** One element on the page that Val has a path for. */
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The colours Val outlines the page's content in, when nobody has said
|
|
298
|
+
* otherwise.
|
|
299
|
+
*
|
|
300
|
+
* They mirror `--bg-page-selection` and `--bg-page-selection-soft` in
|
|
301
|
+
* `packages/ui/spa/index.css`, and they are literals here because the page the
|
|
302
|
+
* bridge runs in has none of Val's stylesheet — no `:host`, no tokens, nothing
|
|
303
|
+
* to inherit. A CSS custom property genuinely cannot reach across that
|
|
304
|
+
* boundary, which is why the colours are SENT (see the `theme` message below)
|
|
305
|
+
* rather than derived.
|
|
306
|
+
*
|
|
307
|
+
* They live beside the protocol rather than with the theme code on purpose:
|
|
308
|
+
* `@valbuild/shared/client` is what the bridge imports, and it exists to keep
|
|
309
|
+
* production bundles small (see the docblock on that entrypoint). Two strings
|
|
310
|
+
* cost nothing; the accent generator that produces the non-default values is
|
|
311
|
+
* ~350 lines of colour maths that no visitor to a Val site should download.
|
|
312
|
+
*
|
|
313
|
+
* `canvasSelectionDefaults.test.ts` holds them equal to the stylesheet.
|
|
314
|
+
*/
|
|
315
|
+
var DEFAULT_CANVAS_SELECTION = "#079455";
|
|
316
|
+
var DEFAULT_CANVAS_SELECTION_SOFT = "rgba(7, 148, 85, 0.4)";
|
|
317
|
+
|
|
318
|
+
/** What the page tells the studio. */
|
|
319
|
+
|
|
320
|
+
/** What the studio tells the page. */
|
|
321
|
+
|
|
322
|
+
/** Narrow an unknown `event.data` to a message from the page. */
|
|
323
|
+
function isValCanvasPageMessage(data) {
|
|
324
|
+
if (createForOfIteratorHelper._typeof(data) !== "object" || data === null) return false;
|
|
325
|
+
var message = data;
|
|
326
|
+
if (message.val !== VAL_CANVAS_MESSAGE) return false;
|
|
327
|
+
return message.type === "ready" || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing" || message.type === "elementPickingStopped" || message.type === "elementPicked" && isPickedElement(data.element) || message.type === "location" && isPageLocation(data) || message.type === "pageRead" && typeof data.requestId === "string" || message.type === "quickPreviewStored" && typeof data.id === "string";
|
|
328
|
+
}
|
|
329
|
+
function isPageLocation(value) {
|
|
330
|
+
var location = value;
|
|
331
|
+
return typeof location.path === "string" && location.path.startsWith("/") && typeof location.scrollX === "number" && typeof location.scrollY === "number";
|
|
332
|
+
}
|
|
333
|
+
function isPickedElement(value) {
|
|
334
|
+
if (createForOfIteratorHelper._typeof(value) !== "object" || value === null) return false;
|
|
335
|
+
var element = value;
|
|
336
|
+
return typeof element.tag === "string" && typeof element.text === "string" && typeof element.selector === "string" && Array.isArray(element.classes) && element.classes.every(function (item) {
|
|
337
|
+
return typeof item === "string";
|
|
338
|
+
}) && (element.valPath === undefined || typeof element.valPath === "string");
|
|
339
|
+
}
|
|
340
|
+
var PICK_MODIFIERS = ["alt", "meta", "shift", "ctrl"];
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Whether a value is a colour a `theme` message may carry.
|
|
344
|
+
*
|
|
345
|
+
* A whitelist rather than an escape, and the reason is where these two strings
|
|
346
|
+
* end up: the bridge writes them into a `<style>` element in the CUSTOMER's
|
|
347
|
+
* document with `dangerouslySetInnerHTML`, so a value containing `</style>` is
|
|
348
|
+
* a script tag away from running in someone else's page. A page can be
|
|
349
|
+
* `postMessage`d by any window holding a handle on it, and this guard is the
|
|
350
|
+
* only thing between such a message and that sink.
|
|
351
|
+
*
|
|
352
|
+
* The two shapes are exactly what `themeCustomProperties` produces — a hex, and
|
|
353
|
+
* the `rgba()` it derives the soft outline as. Narrowing the contract is easier
|
|
354
|
+
* to keep true than sanitising an open one, and a forged message that passes
|
|
355
|
+
* this can do no more than pick a different colour.
|
|
356
|
+
*/
|
|
357
|
+
var CANVAS_SELECTION_COLOR = /^(?:#[0-9a-f]{3,8}|rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(?:,\s*(?:0|1|0?\.\d+)\s*)?\))$/i;
|
|
358
|
+
function isCanvasSelectionColor(value) {
|
|
359
|
+
return typeof value === "string" && CANVAS_SELECTION_COLOR.test(value);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Narrow an unknown `event.data` to a message from the studio. */
|
|
363
|
+
function isValCanvasStudioMessage(data) {
|
|
364
|
+
if (createForOfIteratorHelper._typeof(data) !== "object" || data === null) return false;
|
|
365
|
+
var message = data;
|
|
366
|
+
if (message.val !== VAL_CANVAS_MESSAGE) return false;
|
|
367
|
+
if (message.type === "theme") {
|
|
368
|
+
// Checked here and not in the bridge: a rejected message is never applied
|
|
369
|
+
// at all, and there is one guard rather than one per framework binding.
|
|
370
|
+
return isCanvasSelectionColor(message.selection) && isCanvasSelectionColor(message.selectionSoft);
|
|
371
|
+
}
|
|
372
|
+
if (message.type === "pickElements") {
|
|
373
|
+
var pick = data;
|
|
374
|
+
return typeof pick.enabled === "boolean" && typeof pick.active === "boolean" && typeof pick.keepPicked === "boolean" && PICK_MODIFIERS.includes(pick.modifier);
|
|
375
|
+
}
|
|
376
|
+
if (message.type === "scrollTo") {
|
|
377
|
+
var scroll = data;
|
|
378
|
+
return typeof scroll.x === "number" && Number.isFinite(scroll.x) && typeof scroll.y === "number" && Number.isFinite(scroll.y);
|
|
379
|
+
}
|
|
380
|
+
if (message.type === "quickPreview") {
|
|
381
|
+
var preview = data;
|
|
382
|
+
return typeof preview.id === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(preview.id) && isQuickPreviewFiles(preview.files);
|
|
383
|
+
}
|
|
384
|
+
if (message.type === "readPage") {
|
|
385
|
+
var read = data;
|
|
386
|
+
return typeof read.requestId === "string" && (read.selector === undefined || typeof read.selector === "string") && (read.maxItems === undefined || typeof read.maxItems === "number");
|
|
387
|
+
}
|
|
388
|
+
return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced";
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Whether this document is showing in the studio's canvas frame.
|
|
393
|
+
*
|
|
394
|
+
* The parameter marks the document the studio LOADED, and that is not always
|
|
395
|
+
* the document the frame ends up holding. A frame navigates on its own: a site
|
|
396
|
+
* with a login of its own redirects a signed-out canvas to its sign-in page,
|
|
397
|
+
* and the sign-in sends it back to wherever the site chooses. Each of those is
|
|
398
|
+
* a new document without the parameter, and it used to render Val's whole
|
|
399
|
+
* overlay inside the canvas — a studio in the studio, which never told the
|
|
400
|
+
* canvas it was there, so the canvas never went live again until reloaded.
|
|
401
|
+
*
|
|
402
|
+
* So where the frame SITS answers too: a document whose parent is the studio
|
|
403
|
+
* is in the canvas, whatever its own address says. `parentPathname` is that
|
|
404
|
+
* parent's path: `null` for a top-level document, and for a parent on another
|
|
405
|
+
* origin, whose `location` cannot be read and which is not the studio anyway.
|
|
406
|
+
*/
|
|
407
|
+
function isValCanvasFrame(search) {
|
|
408
|
+
var parentPathname = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
|
|
409
|
+
if (parentPathname !== null && isValStudioPathname(parentPathname)) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
return new URLSearchParams(search).get(VAL_CANVAS_PARAM) !== null;
|
|
414
|
+
} catch (_unused) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Whether a path is the studio's own route, where the canvas lives. */
|
|
420
|
+
function isValStudioPathname(pathname) {
|
|
421
|
+
return pathname === "/val" || pathname.startsWith("/val/");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Add the canvas marker to a page URL. */
|
|
425
|
+
function withValCanvasParam(url) {
|
|
426
|
+
var separator = url.includes("?") ? "&" : "?";
|
|
427
|
+
return "".concat(url).concat(separator).concat(VAL_CANVAS_PARAM, "=1");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Pointing at anything on a page, as Lovable's picker does: hover outlines
|
|
432
|
+
* the element under the pointer, a click picks it, and the assistant is told
|
|
433
|
+
* which (valbuild/home `docs/design-tab.md`).
|
|
434
|
+
*
|
|
435
|
+
* ANY element, not only what Val marks. The design tab changes code, and the
|
|
436
|
+
* thing a person wants changed is as often a wrapper, a gap or a button as a
|
|
437
|
+
* field -- so a field's path is extra information when there is one
|
|
438
|
+
* (`valPath`), never the condition for picking.
|
|
439
|
+
*
|
|
440
|
+
* Picking is on while a MODIFIER is held, so the page stays a page: links
|
|
441
|
+
* follow, menus open, and nothing has to be switched back off. `active`
|
|
442
|
+
* turns it on without one, for a Select button or a touch screen.
|
|
443
|
+
*
|
|
444
|
+
* Plain DOM, nothing of React's or the Studio's, and in `shared` because
|
|
445
|
+
* both halves need it: the canvas bridge (`@valbuild/next`,
|
|
446
|
+
* `@valbuild/tanstack`) runs it inside the proposal's page, which is another
|
|
447
|
+
* origin than the design tab and the only place the page's elements can be
|
|
448
|
+
* reached; the Studio's stories run it on a page of their own. Only the
|
|
449
|
+
* `PickedElement` crosses the frame (`valCanvasProtocol.ts`).
|
|
450
|
+
*/
|
|
451
|
+
|
|
452
|
+
var TEXT_LENGTH$1 = 80;
|
|
453
|
+
var CLASSES = 6;
|
|
454
|
+
function describeElement(element, root) {
|
|
455
|
+
var _element$textContent, _element$closest$getA, _element$closest;
|
|
456
|
+
var text = ((_element$textContent = element.textContent) !== null && _element$textContent !== void 0 ? _element$textContent : "").replace(/\s+/g, " ").trim();
|
|
457
|
+
var valPath = (_element$closest$getA = (_element$closest = element.closest("[data-val-path]")) === null || _element$closest === void 0 ? void 0 : _element$closest.getAttribute("data-val-path")) !== null && _element$closest$getA !== void 0 ? _element$closest$getA : undefined;
|
|
458
|
+
return createForOfIteratorHelper._objectSpread2({
|
|
459
|
+
tag: element.tagName.toLowerCase(),
|
|
460
|
+
text: text.length > TEXT_LENGTH$1 ? "".concat(text.slice(0, TEXT_LENGTH$1 - 1), "\u2026") : text,
|
|
461
|
+
classes: Array.from(element.classList).slice(0, CLASSES),
|
|
462
|
+
selector: selectorOf$1(element, root)
|
|
463
|
+
}, valPath !== undefined ? {
|
|
464
|
+
valPath: valPath
|
|
465
|
+
} : {});
|
|
466
|
+
}
|
|
467
|
+
function selectorOf$1(element, root) {
|
|
468
|
+
var parts = [];
|
|
469
|
+
var at = element;
|
|
470
|
+
while (at !== null && at !== root) {
|
|
471
|
+
var tag = at.tagName.toLowerCase();
|
|
472
|
+
if (at.id !== "") {
|
|
473
|
+
parts.unshift("".concat(tag, "#").concat(at.id));
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
var parent = at.parentElement;
|
|
477
|
+
var same = parent === null ? [] : Array.from(parent.children).filter(function (sibling) {
|
|
478
|
+
var _at;
|
|
479
|
+
return sibling.tagName === ((_at = at) === null || _at === void 0 ? void 0 : _at.tagName);
|
|
480
|
+
});
|
|
481
|
+
parts.unshift(same.length > 1 ? "".concat(tag, ":nth-of-type(").concat(same.indexOf(at) + 1, ")") : tag);
|
|
482
|
+
at = parent;
|
|
483
|
+
}
|
|
484
|
+
return parts.join(" > ");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** What a person is told to hold, on their own keyboard. */
|
|
488
|
+
function modifierLabel(modifier, mac) {
|
|
489
|
+
switch (modifier) {
|
|
490
|
+
case "alt":
|
|
491
|
+
return mac ? "⌥" : "Alt";
|
|
492
|
+
case "meta":
|
|
493
|
+
return mac ? "⌘" : "Win";
|
|
494
|
+
case "shift":
|
|
495
|
+
return mac ? "⇧" : "Shift";
|
|
496
|
+
case "ctrl":
|
|
497
|
+
return mac ? "⌃" : "Ctrl";
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function held(event, modifier) {
|
|
501
|
+
switch (modifier) {
|
|
502
|
+
case "alt":
|
|
503
|
+
return event.altKey;
|
|
504
|
+
case "meta":
|
|
505
|
+
return event.metaKey;
|
|
506
|
+
case "shift":
|
|
507
|
+
return event.shiftKey;
|
|
508
|
+
case "ctrl":
|
|
509
|
+
return event.ctrlKey;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** How far a touch may move and still be a tap, not a scroll. */
|
|
514
|
+
var TAP_SLOP_PX = 10;
|
|
515
|
+
/** How long a touch may be held and still be a tap. */
|
|
516
|
+
var TAP_MS = 600;
|
|
517
|
+
/** How long after a tap its click may still arrive. */
|
|
518
|
+
var TAP_CLICK_MS = 800;
|
|
519
|
+
/**
|
|
520
|
+
* Listens on `root` (capturing, so the page's own handlers never see a pick)
|
|
521
|
+
* and returns what stops it.
|
|
522
|
+
*/
|
|
523
|
+
function attachElementPicker(root, options) {
|
|
524
|
+
var win = root.ownerDocument.defaultView;
|
|
525
|
+
var last = null;
|
|
526
|
+
var picking = function picking(event) {
|
|
527
|
+
return options.active() || held(event, options.modifier);
|
|
528
|
+
};
|
|
529
|
+
var target = function target(event) {
|
|
530
|
+
return event.target instanceof Element && root.contains(event.target) ? event.target : null;
|
|
531
|
+
};
|
|
532
|
+
var onMove = function onMove(event) {
|
|
533
|
+
last = {
|
|
534
|
+
x: event.clientX,
|
|
535
|
+
y: event.clientY
|
|
536
|
+
};
|
|
537
|
+
options.onHover(picking(event) ? target(event) : null);
|
|
538
|
+
};
|
|
539
|
+
/*
|
|
540
|
+
* A tap. iOS Safari dispatches no click for a tap on an element nothing on
|
|
541
|
+
* the way to it listens to -- a listener on the body does not count -- so
|
|
542
|
+
* a tap is taken from the pointer events: a touch that went up where it
|
|
543
|
+
* came down, soon, picks. One that moved is a scroll, which the browser
|
|
544
|
+
* reports as `pointercancel` or as distance, and picks nothing.
|
|
545
|
+
*/
|
|
546
|
+
var down = null;
|
|
547
|
+
/** When a tap last picked: the click a browser may still send is the same tap. */
|
|
548
|
+
var tappedAt = -Infinity;
|
|
549
|
+
var onPointerDown = function onPointerDown(event) {
|
|
550
|
+
down = event.pointerType !== "mouse" && options.active() ? {
|
|
551
|
+
x: event.clientX,
|
|
552
|
+
y: event.clientY,
|
|
553
|
+
at: Date.now()
|
|
554
|
+
} : null;
|
|
555
|
+
};
|
|
556
|
+
var onPointerCancel = function onPointerCancel() {
|
|
557
|
+
down = null;
|
|
558
|
+
};
|
|
559
|
+
var onPointerUp = function onPointerUp(event) {
|
|
560
|
+
var start = down;
|
|
561
|
+
down = null;
|
|
562
|
+
if (start === null || !options.active()) return;
|
|
563
|
+
var moved = Math.hypot(event.clientX - start.x, event.clientY - start.y);
|
|
564
|
+
if (moved > TAP_SLOP_PX || Date.now() - start.at > TAP_MS) return;
|
|
565
|
+
var element = target(event);
|
|
566
|
+
if (element === null) return;
|
|
567
|
+
event.preventDefault();
|
|
568
|
+
event.stopPropagation();
|
|
569
|
+
tappedAt = Date.now();
|
|
570
|
+
options.onHover(element);
|
|
571
|
+
options.onPick(element);
|
|
572
|
+
};
|
|
573
|
+
var onClick = function onClick(event) {
|
|
574
|
+
if (!picking(event)) return;
|
|
575
|
+
var element = target(event);
|
|
576
|
+
if (element === null) return;
|
|
577
|
+
event.preventDefault();
|
|
578
|
+
event.stopPropagation();
|
|
579
|
+
// Already picked by the tap this click comes from.
|
|
580
|
+
if (Date.now() - tappedAt < TAP_CLICK_MS) return;
|
|
581
|
+
options.onPick(element);
|
|
582
|
+
};
|
|
583
|
+
// Pressing the modifier with the pointer still: outline what is under it
|
|
584
|
+
// now, rather than after the next move.
|
|
585
|
+
var onKey = function onKey(event) {
|
|
586
|
+
if (!picking(event)) {
|
|
587
|
+
options.onHover(null);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (last === null) return;
|
|
591
|
+
var under = root.ownerDocument.elementFromPoint(last.x, last.y);
|
|
592
|
+
options.onHover(under !== null && root.contains(under) ? under : null);
|
|
593
|
+
};
|
|
594
|
+
var onLeave = function onLeave() {
|
|
595
|
+
last = null;
|
|
596
|
+
options.onHover(null);
|
|
597
|
+
};
|
|
598
|
+
root.addEventListener("mousemove", onMove, true);
|
|
599
|
+
root.addEventListener("click", onClick, true);
|
|
600
|
+
root.addEventListener("pointerdown", onPointerDown, true);
|
|
601
|
+
root.addEventListener("pointerup", onPointerUp, true);
|
|
602
|
+
root.addEventListener("pointercancel", onPointerCancel, true);
|
|
603
|
+
root.addEventListener("mouseleave", onLeave);
|
|
604
|
+
win === null || win === void 0 || win.addEventListener("keydown", onKey, true);
|
|
605
|
+
win === null || win === void 0 || win.addEventListener("keyup", onKey, true);
|
|
606
|
+
return function () {
|
|
607
|
+
root.removeEventListener("mousemove", onMove, true);
|
|
608
|
+
root.removeEventListener("click", onClick, true);
|
|
609
|
+
root.removeEventListener("pointerdown", onPointerDown, true);
|
|
610
|
+
root.removeEventListener("pointerup", onPointerUp, true);
|
|
611
|
+
root.removeEventListener("pointercancel", onPointerCancel, true);
|
|
612
|
+
root.removeEventListener("mouseleave", onLeave);
|
|
613
|
+
win === null || win === void 0 || win.removeEventListener("keydown", onKey, true);
|
|
614
|
+
win === null || win === void 0 || win.removeEventListener("keyup", onKey, true);
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* The outlines over the page while picking: the element under the pointer,
|
|
620
|
+
* named, and the one picked.
|
|
621
|
+
*
|
|
622
|
+
* Boxes `position: fixed` on top of everything, with `pointer-events: none`,
|
|
623
|
+
* appended to the page's body -- not outlines on the elements themselves.
|
|
624
|
+
* The canvas's field outlines are a stylesheet so they cannot disturb the
|
|
625
|
+
* page's layout (`ValCanvasBridge`); a fixed box is out of the flow, so it
|
|
626
|
+
* cannot either, and it is the one way to put a NAME beside any element, which
|
|
627
|
+
* a selector cannot reach.
|
|
628
|
+
*
|
|
629
|
+
* In a shadow root of one host element: the bridge re-measures the page on
|
|
630
|
+
* every `style` change under `body`, and a box that follows the pointer
|
|
631
|
+
* changes its style every frame. Inside a shadow root those changes are not
|
|
632
|
+
* the page's, so the host's one insertion is all the bridge sees.
|
|
633
|
+
*/
|
|
634
|
+
|
|
635
|
+
var TOP = "2147483647";
|
|
636
|
+
function createPickerOverlay(doc, initialColor) {
|
|
637
|
+
var color = initialColor;
|
|
638
|
+
var shown = {
|
|
639
|
+
hover: null,
|
|
640
|
+
picked: null
|
|
641
|
+
};
|
|
642
|
+
var host = doc.createElement("div");
|
|
643
|
+
host.setAttribute("data-val-picker", "");
|
|
644
|
+
var shadow = host.attachShadow({
|
|
645
|
+
mode: "open"
|
|
646
|
+
});
|
|
647
|
+
doc.body.appendChild(host);
|
|
648
|
+
var box = function box(kind) {
|
|
649
|
+
var el = doc.createElement("div");
|
|
650
|
+
el.setAttribute("data-kind", kind);
|
|
651
|
+
Object.assign(el.style, {
|
|
652
|
+
position: "fixed",
|
|
653
|
+
pointerEvents: "none",
|
|
654
|
+
zIndex: TOP,
|
|
655
|
+
boxSizing: "border-box",
|
|
656
|
+
display: "none"
|
|
657
|
+
});
|
|
658
|
+
shadow.appendChild(el);
|
|
659
|
+
return el;
|
|
660
|
+
};
|
|
661
|
+
var boxes = {
|
|
662
|
+
hover: box("hover"),
|
|
663
|
+
picked: box("picked")
|
|
664
|
+
};
|
|
665
|
+
var label = doc.createElement("span");
|
|
666
|
+
Object.assign(label.style, {
|
|
667
|
+
position: "absolute",
|
|
668
|
+
left: "-2px",
|
|
669
|
+
padding: "1px 4px",
|
|
670
|
+
borderRadius: "2px",
|
|
671
|
+
font: "500 10px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
672
|
+
color: "#fff",
|
|
673
|
+
whiteSpace: "nowrap"
|
|
674
|
+
});
|
|
675
|
+
boxes.hover.appendChild(label);
|
|
676
|
+
// The pointer says it is picking, and a link under it is not a link.
|
|
677
|
+
var cursor = doc.createElement("style");
|
|
678
|
+
cursor.textContent = "[data-val-picking] * { cursor: crosshair !important; }";
|
|
679
|
+
doc.head.appendChild(cursor);
|
|
680
|
+
var place = function place(kind) {
|
|
681
|
+
var element = shown[kind];
|
|
682
|
+
var el = boxes[kind];
|
|
683
|
+
if (element === null || !element.isConnected) {
|
|
684
|
+
el.style.display = "none";
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
var rect = element.getBoundingClientRect();
|
|
688
|
+
Object.assign(el.style, {
|
|
689
|
+
display: "block",
|
|
690
|
+
top: "".concat(rect.top - 2, "px"),
|
|
691
|
+
left: "".concat(rect.left - 2, "px"),
|
|
692
|
+
width: "".concat(rect.width + 4, "px"),
|
|
693
|
+
height: "".concat(rect.height + 4, "px"),
|
|
694
|
+
border: "2px ".concat(kind === "hover" ? "solid" : "dashed", " ").concat(color)
|
|
695
|
+
});
|
|
696
|
+
if (kind === "hover") {
|
|
697
|
+
label.textContent = elementLabel(element);
|
|
698
|
+
label.style.background = color;
|
|
699
|
+
// Above the box, unless that is off the top of the window.
|
|
700
|
+
if (rect.top >= 18) {
|
|
701
|
+
label.style.top = "";
|
|
702
|
+
label.style.bottom = "100%";
|
|
703
|
+
} else {
|
|
704
|
+
label.style.bottom = "";
|
|
705
|
+
label.style.top = "100%";
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
var frame = null;
|
|
710
|
+
var win = doc.defaultView;
|
|
711
|
+
var reposition = function reposition() {
|
|
712
|
+
if (frame !== null || win === null) return;
|
|
713
|
+
frame = win.requestAnimationFrame(function () {
|
|
714
|
+
frame = null;
|
|
715
|
+
place("hover");
|
|
716
|
+
place("picked");
|
|
717
|
+
});
|
|
718
|
+
};
|
|
719
|
+
win === null || win === void 0 || win.addEventListener("scroll", reposition, true);
|
|
720
|
+
win === null || win === void 0 || win.addEventListener("resize", reposition);
|
|
721
|
+
return {
|
|
722
|
+
show: function show(kind, element) {
|
|
723
|
+
shown[kind] = element;
|
|
724
|
+
if (kind === "hover") {
|
|
725
|
+
if (element !== null) {
|
|
726
|
+
doc.documentElement.setAttribute("data-val-picking", "");
|
|
727
|
+
} else {
|
|
728
|
+
doc.documentElement.removeAttribute("data-val-picking");
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
place(kind);
|
|
732
|
+
},
|
|
733
|
+
setColor: function setColor(next) {
|
|
734
|
+
color = next;
|
|
735
|
+
place("hover");
|
|
736
|
+
place("picked");
|
|
737
|
+
},
|
|
738
|
+
destroy: function destroy() {
|
|
739
|
+
if (frame !== null) win === null || win === void 0 || win.cancelAnimationFrame(frame);
|
|
740
|
+
win === null || win === void 0 || win.removeEventListener("scroll", reposition, true);
|
|
741
|
+
win === null || win === void 0 || win.removeEventListener("resize", reposition);
|
|
742
|
+
host.remove();
|
|
743
|
+
cursor.remove();
|
|
744
|
+
doc.documentElement.removeAttribute("data-val-picking");
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/** `h2.mb-6.text-2xl`: what the hover box is named. */
|
|
750
|
+
function elementLabel(element) {
|
|
751
|
+
var classes = Array.from(element.classList).slice(0, 2);
|
|
752
|
+
return [element.tagName.toLowerCase()].concat(createForOfIteratorHelper._toConsumableArray(classes)).join(".");
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Picking on a page, whole: the listeners, the outlines, and Esc to stop
|
|
756
|
+
* picking without the modifier. What a canvas bridge mounts.
|
|
757
|
+
*/
|
|
758
|
+
function startElementPicking(root, options) {
|
|
759
|
+
var _doc$defaultView;
|
|
760
|
+
var doc = root.ownerDocument;
|
|
761
|
+
var overlay = createPickerOverlay(doc, options.color);
|
|
762
|
+
var detach = attachElementPicker(root, {
|
|
763
|
+
modifier: options.modifier,
|
|
764
|
+
active: options.active,
|
|
765
|
+
onHover: function onHover(element) {
|
|
766
|
+
return overlay.show("hover", element);
|
|
767
|
+
},
|
|
768
|
+
onPick: function onPick(element) {
|
|
769
|
+
overlay.show("hover", null);
|
|
770
|
+
overlay.show("picked", element);
|
|
771
|
+
options.onPick(describeElement(element, root));
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
var onKey = function onKey(event) {
|
|
775
|
+
if (event.key === "Escape" && options.active()) options.onStop();
|
|
776
|
+
};
|
|
777
|
+
(_doc$defaultView = doc.defaultView) === null || _doc$defaultView === void 0 || _doc$defaultView.addEventListener("keydown", onKey);
|
|
778
|
+
return {
|
|
779
|
+
clearPicked: function clearPicked() {
|
|
780
|
+
return overlay.show("picked", null);
|
|
781
|
+
},
|
|
782
|
+
setColor: overlay.setColor,
|
|
783
|
+
stop: function stop() {
|
|
784
|
+
var _doc$defaultView2;
|
|
785
|
+
(_doc$defaultView2 = doc.defaultView) === null || _doc$defaultView2 === void 0 || _doc$defaultView2.removeEventListener("keydown", onKey);
|
|
786
|
+
detach();
|
|
787
|
+
overlay.destroy();
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/** Where the page is: what a new build is opened at. */
|
|
793
|
+
|
|
794
|
+
/** A box on the page, in CSS pixels from the top of the document. */
|
|
795
|
+
|
|
796
|
+
/** One thing a reader would notice on the page, in document order. */
|
|
797
|
+
|
|
798
|
+
/** One element in detail: what `read_page` answers for a `selector`. */
|
|
799
|
+
|
|
800
|
+
var TEXT_LENGTH = 160;
|
|
801
|
+
var DEFAULT_MAX_ITEMS = 300;
|
|
802
|
+
var MAX_CHILDREN = 40;
|
|
803
|
+
|
|
804
|
+
/** The computed styles worth reading back: type, colour, spacing, layout. */
|
|
805
|
+
var STYLES = ["display", "position", "font-family", "font-size", "font-weight", "line-height", "letter-spacing", "text-align", "text-transform", "color", "background-color", "background-image", "margin", "padding", "gap", "border", "border-radius", "box-shadow", "width", "height", "max-width", "flex-direction", "justify-content", "align-items", "grid-template-columns", "opacity", "overflow"];
|
|
806
|
+
|
|
807
|
+
/** Where `win` is now. */
|
|
808
|
+
function pageLocationOf(win) {
|
|
809
|
+
var url = new URL(win.location.href);
|
|
810
|
+
// The canvas's own parameter is not part of where the page is.
|
|
811
|
+
url.searchParams["delete"](VAL_CANVAS_PARAM);
|
|
812
|
+
// Nor is which quick preview it shows: the next build is not that one.
|
|
813
|
+
url.searchParams["delete"](QUICK_PREVIEW_PARAM);
|
|
814
|
+
return {
|
|
815
|
+
path: "".concat(url.pathname).concat(url.search).concat(url.hash),
|
|
816
|
+
scrollX: Math.round(win.scrollX),
|
|
817
|
+
scrollY: Math.round(win.scrollY)
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Report where the page is as it moves: on scroll, a little after it settles,
|
|
823
|
+
* and when it navigates -- which a client-side router does without an event,
|
|
824
|
+
* so the address is also looked at now and then. Returns the way to stop.
|
|
825
|
+
*/
|
|
826
|
+
function watchPageLocation(win, onChange, options) {
|
|
827
|
+
var _options$settleMs, _options$pollMs;
|
|
828
|
+
var settleMs = (_options$settleMs = options === null || options === void 0 ? void 0 : options.settleMs) !== null && _options$settleMs !== void 0 ? _options$settleMs : 200;
|
|
829
|
+
var pollMs = (_options$pollMs = options === null || options === void 0 ? void 0 : options.pollMs) !== null && _options$pollMs !== void 0 ? _options$pollMs : 500;
|
|
830
|
+
var last = "";
|
|
831
|
+
var report = function report() {
|
|
832
|
+
var location = pageLocationOf(win);
|
|
833
|
+
var key = JSON.stringify(location);
|
|
834
|
+
if (key === last) return;
|
|
835
|
+
last = key;
|
|
836
|
+
onChange(location);
|
|
837
|
+
};
|
|
838
|
+
var settle = null;
|
|
839
|
+
var onScroll = function onScroll() {
|
|
840
|
+
if (settle !== null) clearTimeout(settle);
|
|
841
|
+
settle = setTimeout(function () {
|
|
842
|
+
settle = null;
|
|
843
|
+
report();
|
|
844
|
+
}, settleMs);
|
|
845
|
+
};
|
|
846
|
+
win.addEventListener("scroll", onScroll, {
|
|
847
|
+
passive: true
|
|
848
|
+
});
|
|
849
|
+
win.addEventListener("popstate", report);
|
|
850
|
+
var poll = setInterval(report, pollMs);
|
|
851
|
+
report();
|
|
852
|
+
return function () {
|
|
853
|
+
win.removeEventListener("scroll", onScroll);
|
|
854
|
+
win.removeEventListener("popstate", report);
|
|
855
|
+
clearInterval(poll);
|
|
856
|
+
if (settle !== null) clearTimeout(settle);
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Scroll to where the last build was, once the page is tall enough to: a page
|
|
862
|
+
* that is still loading its content is shorter than it will be, so this tries
|
|
863
|
+
* again each frame for a while rather than stopping at the bottom of half a
|
|
864
|
+
* page. Returns the way to stop trying.
|
|
865
|
+
*/
|
|
866
|
+
function restoreScroll(win, x, y, options) {
|
|
867
|
+
var _options$withinMs;
|
|
868
|
+
var until = Date.now() + ((_options$withinMs = options === null || options === void 0 ? void 0 : options.withinMs) !== null && _options$withinMs !== void 0 ? _options$withinMs : 2000);
|
|
869
|
+
var frame = null;
|
|
870
|
+
var _attempt = function attempt() {
|
|
871
|
+
frame = null;
|
|
872
|
+
win.scrollTo(x, y);
|
|
873
|
+
var reached = Math.abs(win.scrollY - y) < 2 && Math.abs(win.scrollX - x) < 2;
|
|
874
|
+
if (!reached && Date.now() < until) {
|
|
875
|
+
frame = win.requestAnimationFrame(_attempt);
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
_attempt();
|
|
879
|
+
return function () {
|
|
880
|
+
if (frame !== null) win.cancelAnimationFrame(frame);
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* What is on the page, as a reader would take it in: headings, text, links,
|
|
886
|
+
* buttons, images and inputs, in order, each with where it is. With a
|
|
887
|
+
* `selector`, that element in detail -- its computed styles, how many lines
|
|
888
|
+
* its text wraps to, and its children.
|
|
889
|
+
*
|
|
890
|
+
* `root` is what selectors are written from: the body, as `describeElement`
|
|
891
|
+
* writes them for a picked element.
|
|
892
|
+
*/
|
|
893
|
+
function readPageSnapshot(doc, options) {
|
|
894
|
+
var _options$maxItems, _win$scrollY, _win$location$href, _win$innerWidth, _win$innerHeight;
|
|
895
|
+
var win = doc.defaultView;
|
|
896
|
+
var root = doc.body;
|
|
897
|
+
var maxItems = (_options$maxItems = options === null || options === void 0 ? void 0 : options.maxItems) !== null && _options$maxItems !== void 0 ? _options$maxItems : DEFAULT_MAX_ITEMS;
|
|
898
|
+
var outline = [];
|
|
899
|
+
var truncated = false;
|
|
900
|
+
var scope = (options === null || options === void 0 ? void 0 : options.selector) !== undefined ? findBySelector(root, options.selector) : null;
|
|
901
|
+
var walkRoot = scope !== null && scope !== void 0 ? scope : root;
|
|
902
|
+
// A hidden element is skipped with all that is in it.
|
|
903
|
+
var walker = doc.createTreeWalker(walkRoot, NodeFilter.SHOW_ELEMENT, {
|
|
904
|
+
acceptNode: function acceptNode(candidate) {
|
|
905
|
+
return candidate instanceof Element && isHidden(candidate, win) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
var node = walker.currentNode;
|
|
909
|
+
while (node !== null) {
|
|
910
|
+
if (node instanceof Element) {
|
|
911
|
+
var item = outlineItemOf(node, root, win);
|
|
912
|
+
if (item !== null) {
|
|
913
|
+
if (outline.length >= maxItems) {
|
|
914
|
+
truncated = true;
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
outline.push(item);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
node = walker.nextNode();
|
|
921
|
+
}
|
|
922
|
+
var scrollY = Math.round((_win$scrollY = win === null || win === void 0 ? void 0 : win.scrollY) !== null && _win$scrollY !== void 0 ? _win$scrollY : 0);
|
|
923
|
+
return createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({
|
|
924
|
+
url: (_win$location$href = win === null || win === void 0 ? void 0 : win.location.href) !== null && _win$location$href !== void 0 ? _win$location$href : "",
|
|
925
|
+
title: doc.title,
|
|
926
|
+
viewport: {
|
|
927
|
+
width: (_win$innerWidth = win === null || win === void 0 ? void 0 : win.innerWidth) !== null && _win$innerWidth !== void 0 ? _win$innerWidth : 0,
|
|
928
|
+
height: (_win$innerHeight = win === null || win === void 0 ? void 0 : win.innerHeight) !== null && _win$innerHeight !== void 0 ? _win$innerHeight : 0,
|
|
929
|
+
scrollY: scrollY,
|
|
930
|
+
pageHeight: Math.round(doc.documentElement.scrollHeight)
|
|
931
|
+
},
|
|
932
|
+
outline: outline
|
|
933
|
+
}, truncated ? {
|
|
934
|
+
truncated: true
|
|
935
|
+
} : {}), scope !== null ? {
|
|
936
|
+
element: detailOf(scope, root, win)
|
|
937
|
+
} : {}), (options === null || options === void 0 ? void 0 : options.selector) !== undefined && scope === null ? {
|
|
938
|
+
notFound: options.selector
|
|
939
|
+
} : {});
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* The element a selector names, written as `describeElement` writes them --
|
|
944
|
+
* from the body down, or from an element with an id -- or any selector a
|
|
945
|
+
* person would write.
|
|
946
|
+
*/
|
|
947
|
+
function findBySelector(root, selector) {
|
|
948
|
+
try {
|
|
949
|
+
var _root$querySelector;
|
|
950
|
+
return (_root$querySelector = root.querySelector(":scope > ".concat(selector))) !== null && _root$querySelector !== void 0 ? _root$querySelector : root.ownerDocument.querySelector(selector);
|
|
951
|
+
} catch (_unused) {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function outlineItemOf(element, root, win) {
|
|
956
|
+
var tag = element.tagName.toLowerCase();
|
|
957
|
+
var base = function base() {
|
|
958
|
+
return {
|
|
959
|
+
tag: tag,
|
|
960
|
+
selector: selectorOf(element, root),
|
|
961
|
+
box: boxOf(element, win)
|
|
962
|
+
};
|
|
963
|
+
};
|
|
964
|
+
var heading = /^h([1-6])$/.exec(tag);
|
|
965
|
+
if (heading !== null) {
|
|
966
|
+
return createForOfIteratorHelper._objectSpread2({
|
|
967
|
+
kind: "heading",
|
|
968
|
+
level: Number(heading[1]),
|
|
969
|
+
text: textOf(element)
|
|
970
|
+
}, base());
|
|
971
|
+
}
|
|
972
|
+
if (tag === "a") {
|
|
973
|
+
var _element$getAttribute;
|
|
974
|
+
var href = (_element$getAttribute = element.getAttribute("href")) !== null && _element$getAttribute !== void 0 ? _element$getAttribute : undefined;
|
|
975
|
+
return createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({
|
|
976
|
+
kind: "link"
|
|
977
|
+
}, textOf(element) !== "" ? {
|
|
978
|
+
text: textOf(element)
|
|
979
|
+
} : {}), href !== undefined ? {
|
|
980
|
+
href: href
|
|
981
|
+
} : {}), base());
|
|
982
|
+
}
|
|
983
|
+
if (tag === "button" || element.getAttribute("role") === "button") {
|
|
984
|
+
return createForOfIteratorHelper._objectSpread2({
|
|
985
|
+
kind: "button",
|
|
986
|
+
text: textOf(element)
|
|
987
|
+
}, base());
|
|
988
|
+
}
|
|
989
|
+
if (tag === "img") {
|
|
990
|
+
var alt = element.getAttribute("alt");
|
|
991
|
+
var src = element.getAttribute("src");
|
|
992
|
+
return createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({
|
|
993
|
+
kind: "image"
|
|
994
|
+
}, alt !== null ? {
|
|
995
|
+
alt: alt
|
|
996
|
+
} : {}), src !== null ? {
|
|
997
|
+
src: shorten(src)
|
|
998
|
+
} : {}), base());
|
|
999
|
+
}
|
|
1000
|
+
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
1001
|
+
var _ref, _ref2, _element$getAttribute2;
|
|
1002
|
+
var label = (_ref = (_ref2 = (_element$getAttribute2 = element.getAttribute("aria-label")) !== null && _element$getAttribute2 !== void 0 ? _element$getAttribute2 : element.getAttribute("placeholder")) !== null && _ref2 !== void 0 ? _ref2 : element.getAttribute("name")) !== null && _ref !== void 0 ? _ref : "";
|
|
1003
|
+
return createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({
|
|
1004
|
+
kind: "input"
|
|
1005
|
+
}, label !== "" ? {
|
|
1006
|
+
text: label
|
|
1007
|
+
} : {}), base());
|
|
1008
|
+
}
|
|
1009
|
+
if (tag === "header" || tag === "nav" || tag === "main" || tag === "section" || tag === "footer" || tag === "aside") {
|
|
1010
|
+
return createForOfIteratorHelper._objectSpread2({
|
|
1011
|
+
kind: "section"
|
|
1012
|
+
}, base());
|
|
1013
|
+
}
|
|
1014
|
+
// Text: an element that holds words of its own, not only through children
|
|
1015
|
+
// already listed (a heading, a link) -- a paragraph, a list item, a label.
|
|
1016
|
+
if (tag === "p" || tag === "li" || tag === "blockquote" || tag === "figcaption" || tag === "label" || tag === "td" || tag === "th" || hasOwnText(element)) {
|
|
1017
|
+
var text = textOf(element);
|
|
1018
|
+
if (text === "" || insideListed(element)) return null;
|
|
1019
|
+
return createForOfIteratorHelper._objectSpread2({
|
|
1020
|
+
kind: "text",
|
|
1021
|
+
text: text
|
|
1022
|
+
}, base());
|
|
1023
|
+
}
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Already part of something the outline names: a link's or a button's words,
|
|
1029
|
+
* or a `<strong>` in a paragraph that is listed whole.
|
|
1030
|
+
*/
|
|
1031
|
+
function insideListed(element) {
|
|
1032
|
+
var parent = element.parentElement;
|
|
1033
|
+
return parent !== null && parent.closest("a, button, h1, h2, h3, h4, h5, h6, [role='button'], p, li, blockquote, figcaption, label, td, th") !== null;
|
|
1034
|
+
}
|
|
1035
|
+
function hasOwnText(element) {
|
|
1036
|
+
for (var _i = 0, _Array$from = Array.from(element.childNodes); _i < _Array$from.length; _i++) {
|
|
1037
|
+
var _child$textContent;
|
|
1038
|
+
var child = _Array$from[_i];
|
|
1039
|
+
if (child.nodeType === 3 && ((_child$textContent = child.textContent) !== null && _child$textContent !== void 0 ? _child$textContent : "").trim() !== "") {
|
|
1040
|
+
return true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
function detailOf(element, root, win) {
|
|
1046
|
+
var _win$getComputedStyle;
|
|
1047
|
+
var styles = {};
|
|
1048
|
+
var computed = (_win$getComputedStyle = win === null || win === void 0 ? void 0 : win.getComputedStyle(element)) !== null && _win$getComputedStyle !== void 0 ? _win$getComputedStyle : null;
|
|
1049
|
+
if (computed !== null) {
|
|
1050
|
+
var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(STYLES),
|
|
1051
|
+
_step;
|
|
1052
|
+
try {
|
|
1053
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
1054
|
+
var property = _step.value;
|
|
1055
|
+
var value = computed.getPropertyValue(property);
|
|
1056
|
+
if (value !== "" && value !== "none" && value !== "normal") {
|
|
1057
|
+
styles[property] = value;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
_iterator.e(err);
|
|
1062
|
+
} finally {
|
|
1063
|
+
_iterator.f();
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
var box = boxOf(element, win);
|
|
1067
|
+
var lineHeight = computed !== null ? parseFloat(computed.getPropertyValue("line-height")) : NaN;
|
|
1068
|
+
var lines = Number.isFinite(lineHeight) && lineHeight > 0 && box.height > 0 ? Math.max(1, Math.round(box.height / lineHeight)) : undefined;
|
|
1069
|
+
return createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({
|
|
1070
|
+
selector: selectorOf(element, root),
|
|
1071
|
+
tag: element.tagName.toLowerCase(),
|
|
1072
|
+
text: textOf(element),
|
|
1073
|
+
box: box
|
|
1074
|
+
}, lines !== undefined ? {
|
|
1075
|
+
lines: lines
|
|
1076
|
+
} : {}), {}, {
|
|
1077
|
+
styles: styles,
|
|
1078
|
+
children: Array.from(element.children).slice(0, MAX_CHILDREN).map(function (child) {
|
|
1079
|
+
var text = textOf(child);
|
|
1080
|
+
return createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({
|
|
1081
|
+
tag: child.tagName.toLowerCase(),
|
|
1082
|
+
selector: selectorOf(child, root)
|
|
1083
|
+
}, text !== "" ? {
|
|
1084
|
+
text: text
|
|
1085
|
+
} : {}), {}, {
|
|
1086
|
+
box: boxOf(child, win)
|
|
1087
|
+
});
|
|
1088
|
+
})
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
function isHidden(element, win) {
|
|
1092
|
+
var tag = element.tagName.toLowerCase();
|
|
1093
|
+
if (tag === "script" || tag === "style" || tag === "noscript" || tag === "template") {
|
|
1094
|
+
return true;
|
|
1095
|
+
}
|
|
1096
|
+
// The picker's own outlines, and what the page hides from everyone.
|
|
1097
|
+
if (element.hasAttribute("data-val-picker") || element.hasAttribute("hidden") || element.getAttribute("aria-hidden") === "true") {
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
var style = win === null || win === void 0 ? void 0 : win.getComputedStyle(element);
|
|
1101
|
+
return (style === null || style === void 0 ? void 0 : style.display) === "none" || (style === null || style === void 0 ? void 0 : style.visibility) === "hidden";
|
|
1102
|
+
}
|
|
1103
|
+
function boxOf(element, win) {
|
|
1104
|
+
var _win$scrollX, _win$scrollY2;
|
|
1105
|
+
var rect = element.getBoundingClientRect();
|
|
1106
|
+
return {
|
|
1107
|
+
x: Math.round(rect.left + ((_win$scrollX = win === null || win === void 0 ? void 0 : win.scrollX) !== null && _win$scrollX !== void 0 ? _win$scrollX : 0)),
|
|
1108
|
+
y: Math.round(rect.top + ((_win$scrollY2 = win === null || win === void 0 ? void 0 : win.scrollY) !== null && _win$scrollY2 !== void 0 ? _win$scrollY2 : 0)),
|
|
1109
|
+
width: Math.round(rect.width),
|
|
1110
|
+
height: Math.round(rect.height)
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function textOf(element) {
|
|
1114
|
+
var _element$textContent;
|
|
1115
|
+
return shorten(((_element$textContent = element.textContent) !== null && _element$textContent !== void 0 ? _element$textContent : "").replace(/\s+/g, " ").trim());
|
|
1116
|
+
}
|
|
1117
|
+
function shorten(text) {
|
|
1118
|
+
return text.length > TEXT_LENGTH ? "".concat(text.slice(0, TEXT_LENGTH - 1), "\u2026") : text;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/** As `describeElement` writes it: from `root` down, or from an id. */
|
|
1122
|
+
function selectorOf(element, root) {
|
|
1123
|
+
var parts = [];
|
|
1124
|
+
var at = element;
|
|
1125
|
+
while (at !== null && at !== root) {
|
|
1126
|
+
var tag = at.tagName.toLowerCase();
|
|
1127
|
+
if (at.id !== "") {
|
|
1128
|
+
parts.unshift("".concat(tag, "#").concat(at.id));
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
var parent = at.parentElement;
|
|
1132
|
+
var same = parent === null ? [] : Array.from(parent.children).filter(function (sibling) {
|
|
1133
|
+
var _at;
|
|
1134
|
+
return sibling.tagName === ((_at = at) === null || _at === void 0 ? void 0 : _at.tagName);
|
|
1135
|
+
});
|
|
1136
|
+
parts.unshift(same.length > 1 ? "".concat(tag, ":nth-of-type(").concat(same.indexOf(at) + 1, ")") : tag);
|
|
1137
|
+
at = parent;
|
|
1138
|
+
}
|
|
1139
|
+
return parts.join(" > ");
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function parseRoutePattern(pattern) {
|
|
1143
|
+
if (pattern === "" || pattern === "/") {
|
|
1144
|
+
return [];
|
|
1145
|
+
}
|
|
1146
|
+
var p = pattern.startsWith("/") ? pattern.slice(1) : pattern;
|
|
1147
|
+
return p.split("/").map(function (part) {
|
|
1148
|
+
var isOptionalParam = part.startsWith("[[") && part.endsWith("]]");
|
|
1149
|
+
var isNonOptionalParam = part.startsWith("[") && part.endsWith("]");
|
|
1150
|
+
var nameStart = isOptionalParam ? 2 : isNonOptionalParam ? 1 : 0;
|
|
1151
|
+
var nameEnd = part.indexOf("]", nameStart);
|
|
1152
|
+
var isArrayParam = part.slice(nameStart, nameStart + 3) === "...";
|
|
1153
|
+
var paramName = part.slice(nameStart + (isArrayParam ? 3 : 0), nameEnd);
|
|
1154
|
+
if (isOptionalParam || isNonOptionalParam) {
|
|
1155
|
+
if (isArrayParam) {
|
|
1156
|
+
return {
|
|
1157
|
+
type: "array-param",
|
|
1158
|
+
paramName: paramName,
|
|
1159
|
+
optional: isOptionalParam
|
|
1160
|
+
};
|
|
1161
|
+
} else {
|
|
1162
|
+
return {
|
|
1163
|
+
type: "string-param",
|
|
1164
|
+
paramName: paramName,
|
|
1165
|
+
optional: isOptionalParam
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
type: "literal",
|
|
1171
|
+
name: part
|
|
1172
|
+
};
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* A parsed pattern written back as segments: the inverse of
|
|
1178
|
+
* {@link parseRoutePattern}, in the vocabulary core's `matchRoutePattern`
|
|
1179
|
+
* reads.
|
|
1180
|
+
*/
|
|
1181
|
+
function formatRoutePattern(pattern) {
|
|
1182
|
+
return pattern.map(function (part) {
|
|
1183
|
+
if (part.type === "literal") {
|
|
1184
|
+
return part.name;
|
|
1185
|
+
}
|
|
1186
|
+
var inner = part.type === "string-param" ? part.paramName : "...".concat(part.paramName);
|
|
1187
|
+
return part.optional ? "[[".concat(inner, "]]") : "[".concat(inner, "]");
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function getNextAppRouterSourceFolder(moduleFilePath) {
|
|
1192
|
+
if (moduleFilePath.startsWith("/app")) {
|
|
1193
|
+
return "/app";
|
|
1194
|
+
} else if (moduleFilePath.startsWith("/src/app")) {
|
|
1195
|
+
return "/src/app";
|
|
1196
|
+
}
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* The folder a TanStack Router app keeps its route files in.
|
|
1202
|
+
*
|
|
1203
|
+
* The same question as above for the other convention: `routes/` at the project
|
|
1204
|
+
* root, or under `src/`. A Val module for a route lives beside the route file
|
|
1205
|
+
* and is named the same way, so this is the prefix to strip before reading the
|
|
1206
|
+
* route pattern out of the rest.
|
|
1207
|
+
*/
|
|
1208
|
+
function getTanStackRouterSourceFolder(moduleFilePath) {
|
|
1209
|
+
if (moduleFilePath.startsWith("/routes/")) {
|
|
1210
|
+
return "/routes";
|
|
1211
|
+
} else if (moduleFilePath.startsWith("/src/routes/")) {
|
|
1212
|
+
return "/src/routes";
|
|
1213
|
+
}
|
|
1214
|
+
return null;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* The route folder of whichever page router this module belongs to.
|
|
1219
|
+
*
|
|
1220
|
+
* The two conventions have disjoint prefixes — `app/` versus `routes/` — so a
|
|
1221
|
+
* module file path answers this on its own, which is what the Studio needs: it
|
|
1222
|
+
* walks every schema in the project and has no framework to ask.
|
|
1223
|
+
*/
|
|
1224
|
+
function getPageRouterSourceFolder(moduleFilePath) {
|
|
1225
|
+
var _getNextAppRouterSour;
|
|
1226
|
+
return (_getNextAppRouterSour = getNextAppRouterSourceFolder(moduleFilePath)) !== null && _getNextAppRouterSour !== void 0 ? _getNextAppRouterSour : getTanStackRouterSourceFolder(moduleFilePath);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Whether a route folder is a TanStack Router one.
|
|
1231
|
+
*
|
|
1232
|
+
* Exported so the pattern derivation and the source-folder detection cannot
|
|
1233
|
+
* drift apart about what `/src/routes` means.
|
|
1234
|
+
*/
|
|
1235
|
+
function isTanStackRoutesFolder(srcFolder) {
|
|
1236
|
+
return srcFolder === "/routes" || srcFolder === "/src/routes";
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Strictly speaking this should be in the framework packages but it's shared,
|
|
1240
|
+
// because we want to use it in the ui package. We want to resolve that somehow.
|
|
1241
|
+
//
|
|
1242
|
+
// It is one function for both page routers because the tree it builds is made
|
|
1243
|
+
// of URL paths, which is the same thing in either: only `srcFolder` and the
|
|
1244
|
+
// pattern derivation differ, and both of those are arguments (or the one call
|
|
1245
|
+
// below).
|
|
1246
|
+
function getPageRouterSitemapTree(srcFolder, paths) {
|
|
1247
|
+
var root = {
|
|
1248
|
+
type: "node",
|
|
1249
|
+
name: "/",
|
|
1250
|
+
children: []
|
|
1251
|
+
};
|
|
1252
|
+
var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(paths),
|
|
1253
|
+
_step;
|
|
1254
|
+
try {
|
|
1255
|
+
var _loop = function _loop() {
|
|
1256
|
+
var path = _step.value;
|
|
1257
|
+
var urlPath = path.urlPath,
|
|
1258
|
+
moduleFilePathS = path.moduleFilePath;
|
|
1259
|
+
var moduleFilePath = moduleFilePathS;
|
|
1260
|
+
if (!urlPath.startsWith("/")) {
|
|
1261
|
+
core.Internal.log.error("urlPath must start with /: ".concat(urlPath));
|
|
1262
|
+
return 0; // continue
|
|
1263
|
+
}
|
|
1264
|
+
var pattern = getPatternFromModuleFilePath(moduleFilePath, srcFolder);
|
|
1265
|
+
if (urlPath === "/") {
|
|
1266
|
+
var fullPath = "/";
|
|
1267
|
+
root.pattern = getPatternFromModuleFilePath(moduleFilePath, srcFolder);
|
|
1268
|
+
root.page = {
|
|
1269
|
+
fullPath: fullPath
|
|
1270
|
+
};
|
|
1271
|
+
root.sourcePath = core.Internal.joinModuleFilePathAndModulePath(moduleFilePath, "\"".concat(fullPath, "\""));
|
|
1272
|
+
root.moduleFilePath = moduleFilePath;
|
|
1273
|
+
return 0; // continue
|
|
1274
|
+
}
|
|
1275
|
+
var pathParts = urlPath.split("/").slice(1).filter(Boolean);
|
|
1276
|
+
var currentNode = root;
|
|
1277
|
+
pathParts.forEach(function (part, index) {
|
|
1278
|
+
var hasChildren = index < pathParts.length - 1;
|
|
1279
|
+
var isLast = index === pathParts.length - 1;
|
|
1280
|
+
var fullPath = "/" + pathParts.slice(0, index + 1).join("/");
|
|
1281
|
+
var sourcePath = core.Internal.joinModuleFilePathAndModulePath(moduleFilePath, "\"".concat(fullPath, "\""));
|
|
1282
|
+
var node = hasChildren ? createForOfIteratorHelper._objectSpread2(createForOfIteratorHelper._objectSpread2({
|
|
1283
|
+
type: "node",
|
|
1284
|
+
name: part,
|
|
1285
|
+
pattern: pattern,
|
|
1286
|
+
moduleFilePath: moduleFilePath
|
|
1287
|
+
}, isLast ? {
|
|
1288
|
+
page: {
|
|
1289
|
+
fullPath: fullPath
|
|
1290
|
+
},
|
|
1291
|
+
sourcePath: sourcePath
|
|
1292
|
+
} : {}), {}, {
|
|
1293
|
+
children: []
|
|
1294
|
+
}) : {
|
|
1295
|
+
type: "leaf",
|
|
1296
|
+
name: part,
|
|
1297
|
+
pattern: pattern,
|
|
1298
|
+
fullPath: fullPath,
|
|
1299
|
+
moduleFilePath: moduleFilePath,
|
|
1300
|
+
sourcePath: sourcePath,
|
|
1301
|
+
children: []
|
|
1302
|
+
};
|
|
1303
|
+
var existingNodeIndex = currentNode.children.findIndex(function (node) {
|
|
1304
|
+
return node.name === part;
|
|
1305
|
+
});
|
|
1306
|
+
if (existingNodeIndex === -1) {
|
|
1307
|
+
currentNode.children.push(node);
|
|
1308
|
+
if (currentNode.type === "node") {
|
|
1309
|
+
currentNode.isLinear = true;
|
|
1310
|
+
if (currentNode.children.length > 1) {
|
|
1311
|
+
delete currentNode.isLinear;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
currentNode = node;
|
|
1315
|
+
} else {
|
|
1316
|
+
var existingNode = currentNode.children[existingNodeIndex];
|
|
1317
|
+
if (existingNode.type === "leaf" && hasChildren) {
|
|
1318
|
+
var _sourcePath = existingNode.sourcePath;
|
|
1319
|
+
// convert leaf to node
|
|
1320
|
+
currentNode.children[existingNodeIndex] = {
|
|
1321
|
+
type: "node",
|
|
1322
|
+
name: part,
|
|
1323
|
+
pattern: pattern,
|
|
1324
|
+
page: {
|
|
1325
|
+
fullPath: fullPath
|
|
1326
|
+
},
|
|
1327
|
+
sourcePath: _sourcePath,
|
|
1328
|
+
children: existingNode.children,
|
|
1329
|
+
moduleFilePath: moduleFilePath
|
|
1330
|
+
};
|
|
1331
|
+
currentNode = currentNode.children[existingNodeIndex];
|
|
1332
|
+
} else if (existingNode.type === "node" && isLast) {
|
|
1333
|
+
// add page, source path and module file path
|
|
1334
|
+
existingNode.page = {
|
|
1335
|
+
fullPath: fullPath
|
|
1336
|
+
};
|
|
1337
|
+
existingNode.sourcePath = sourcePath;
|
|
1338
|
+
existingNode.moduleFilePath = moduleFilePath;
|
|
1339
|
+
existingNode.pattern = pattern;
|
|
1340
|
+
} else {
|
|
1341
|
+
currentNode = currentNode.children[existingNodeIndex];
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
},
|
|
1346
|
+
_ret;
|
|
1347
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
1348
|
+
_ret = _loop();
|
|
1349
|
+
if (_ret === 0) continue;
|
|
1350
|
+
}
|
|
1351
|
+
} catch (err) {
|
|
1352
|
+
_iterator.e(err);
|
|
1353
|
+
} finally {
|
|
1354
|
+
_iterator.f();
|
|
1355
|
+
}
|
|
1356
|
+
return root;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
/**
|
|
1360
|
+
* The route pattern a page-router module serves, e.g. `/blogs/[blog]`.
|
|
1361
|
+
*
|
|
1362
|
+
* Written in Next's vocabulary for both routers on purpose: the pattern is read
|
|
1363
|
+
* by `parseRoutePattern`, the sitemap and the Studio's key inputs, none of which
|
|
1364
|
+
* should have to know which framework the project uses. The TanStack branch
|
|
1365
|
+
* translates that framework's conventions into it; see
|
|
1366
|
+
* `tanStackSegmentsOfRoutePath` in `@valbuild/core` for the rules.
|
|
1367
|
+
*/
|
|
1368
|
+
function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
|
|
1369
|
+
if (isTanStackRoutesFolder(srcFolder)) {
|
|
1370
|
+
var routePath = moduleFilePath.slice(srcFolder.length).replace(/\.val\.[tj]sx?$/, "");
|
|
1371
|
+
var segments = core.tanStackSegmentsOfRoutePath(routePath);
|
|
1372
|
+
// "" rather than "/" for the root, so it matches what the Next branch
|
|
1373
|
+
// returns for `/app/page.val.ts` — `parseRoutePattern` reads both as [].
|
|
1374
|
+
return segments.length === 0 ? "" : "/".concat(segments.join("/"));
|
|
1375
|
+
}
|
|
1376
|
+
return moduleFilePath.replace(srcFolder, "")
|
|
1377
|
+
// TODO: hacky!
|
|
1378
|
+
// remove route groups
|
|
1379
|
+
.replace(/\/\([^/]+\)/g, "") // not sure we want to do this?
|
|
1380
|
+
.replace("/page.val.ts", "").replace("/page.val.js", "");
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
exports.DEFAULT_CANVAS_SELECTION = DEFAULT_CANVAS_SELECTION;
|
|
1384
|
+
exports.DEFAULT_CANVAS_SELECTION_SOFT = DEFAULT_CANVAS_SELECTION_SOFT;
|
|
1385
|
+
exports.QUICK_PREVIEW_PARAM = QUICK_PREVIEW_PARAM;
|
|
1386
|
+
exports.QUICK_PREVIEW_SERVICE_WORKER_PATH = QUICK_PREVIEW_SERVICE_WORKER_PATH;
|
|
1387
|
+
exports.QUICK_PREVIEW_SERVICE_WORKER_SOURCE = QUICK_PREVIEW_SERVICE_WORKER_SOURCE;
|
|
1388
|
+
exports.QUICK_PREVIEW_STORE_MESSAGE = QUICK_PREVIEW_STORE_MESSAGE;
|
|
1389
|
+
exports.VAL_AI_SESSION_STORAGE_KEY = VAL_AI_SESSION_STORAGE_KEY;
|
|
1390
|
+
exports.VAL_CANVAS_MESSAGE = VAL_CANVAS_MESSAGE;
|
|
1391
|
+
exports.VAL_CANVAS_PARAM = VAL_CANVAS_PARAM;
|
|
1392
|
+
exports.VAL_CONFIG_SESSION_STORAGE_KEY = VAL_CONFIG_SESSION_STORAGE_KEY;
|
|
1393
|
+
exports.VAL_THEME_SESSION_STORAGE_KEY = VAL_THEME_SESSION_STORAGE_KEY;
|
|
1394
|
+
exports.attachElementPicker = attachElementPicker;
|
|
1395
|
+
exports.createPickerOverlay = createPickerOverlay;
|
|
1396
|
+
exports.describeElement = describeElement;
|
|
1397
|
+
exports.elementLabel = elementLabel;
|
|
1398
|
+
exports.findBySelector = findBySelector;
|
|
1399
|
+
exports.formatRoutePattern = formatRoutePattern;
|
|
1400
|
+
exports.getNextAppRouterSourceFolder = getNextAppRouterSourceFolder;
|
|
1401
|
+
exports.getPageRouterSitemapTree = getPageRouterSitemapTree;
|
|
1402
|
+
exports.getPageRouterSourceFolder = getPageRouterSourceFolder;
|
|
1403
|
+
exports.getPatternFromModuleFilePath = getPatternFromModuleFilePath;
|
|
1404
|
+
exports.getTanStackRouterSourceFolder = getTanStackRouterSourceFolder;
|
|
1405
|
+
exports.isDesignTabOrigin = isDesignTabOrigin;
|
|
1406
|
+
exports.isQuickPreviewFiles = isQuickPreviewFiles;
|
|
1407
|
+
exports.isTanStackRoutesFolder = isTanStackRoutesFolder;
|
|
1408
|
+
exports.isValCanvasFrame = isValCanvasFrame;
|
|
1409
|
+
exports.isValCanvasPageMessage = isValCanvasPageMessage;
|
|
1410
|
+
exports.isValCanvasStudioMessage = isValCanvasStudioMessage;
|
|
1411
|
+
exports.modifierLabel = modifierLabel;
|
|
1412
|
+
exports.pageLocationOf = pageLocationOf;
|
|
1413
|
+
exports.parseRoutePattern = parseRoutePattern;
|
|
1414
|
+
exports.quickPreviewSiteOrigin = quickPreviewSiteOrigin;
|
|
1415
|
+
exports.readPageSnapshot = readPageSnapshot;
|
|
1416
|
+
exports.restoreScroll = restoreScroll;
|
|
1417
|
+
exports.startElementPicking = startElementPicking;
|
|
1418
|
+
exports.storeQuickPreview = storeQuickPreview;
|
|
1419
|
+
exports.watchPageLocation = watchPageLocation;
|
|
1420
|
+
exports.withQuickPreviewParam = withQuickPreviewParam;
|
|
1421
|
+
exports.withValCanvasParam = withValCanvasParam;
|