@deot/docs-playground 1.1.1 → 1.1.3

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.
@@ -0,0 +1,767 @@
1
+ import { a as DEFAULT_CDN_URL, n as createBuiltinImports, r as createBuiltinStyles, s as NEW_SFC_CODE } from "./cdn-DEvdGJu2.js";
2
+ import { r as whenSassReady, t as bindPlaygroundScss } from "./scss-Co2it4vB.js";
3
+ import { nextTick, onBeforeUnmount, ref, unref, watch } from "vue";
4
+ import { File, useStore } from "@vue/repl";
5
+ //#region packages/playground/src/core/preview-scroll.ts
6
+ /** iframe 包装成功后挂在 `html` 上,供样式与 auto-height 识别。 */
7
+ var PREVIEW_SCROLL_HTML_CLASS = "docs-playground-preview-scroll";
8
+ /** Scroller 内容节点 class,auto-height 优先测量该节点。 */
9
+ var PREVIEW_SCROLL_CONTENT_CLASS = "docs-playground-preview-scroll__content";
10
+ /** 重评时先清掉 html class,避免 `await import` 期间仍套用 `height:100%`。 */
11
+ var PREVIEW_SCROLL_RESET_CODE = `document.documentElement.classList.remove("${PREVIEW_SCROLL_HTML_CLASS}");`;
12
+ /** 动态探测 `@deot/vc`;失败时为空对象,不阻断 DocsLink / 预览挂载。 */
13
+ var PREVIEW_SCROLLER_IMPORT_CODE = "const __docsVc=await import(\"@deot/vc\").catch(()=>({}))";
14
+ /**
15
+ * Vue 的 `mount` 闭包的是 `createApp(root)` 时的根组件,改 `app._component` 不会生效。
16
+ * 因此劫持 `app.mount`:等全部 useCode 注册完后,再 `createApp` 包一层 Scroller。
17
+ * 前导分号避免接在 `app.component(...)` 后被 ASI 解析成调用返回值。
18
+ */
19
+ var PREVIEW_SCROLLER_USE_CODE = [
20
+ ";(()=>{",
21
+ PREVIEW_SCROLL_RESET_CODE,
22
+ "const __DocsScroller=__docsVc&&__docsVc.Scroller;",
23
+ "if(!__DocsScroller)return;",
24
+ "const UserApp=app._component;",
25
+ "app.mount=(container,...rest)=>{",
26
+ "const next=_createApp({",
27
+ "name:\"DocsPreviewRoot\",",
28
+ "setup(){return()=>__docsH(__DocsScroller,{",
29
+ "height:\"100%\",",
30
+ "native:false,",
31
+ "autoResize:true,",
32
+ "showBar:true,",
33
+ `contentClass:"${PREVIEW_SCROLL_CONTENT_CLASS}"`,
34
+ "},{default:()=>__docsH(UserApp)})}",
35
+ "});",
36
+ "next.config.errorHandler=app.config.errorHandler;",
37
+ "if(app._context&&next._context){",
38
+ "if(app._context.components)Object.assign(next._context.components,app._context.components);",
39
+ "if(app._context.directives)Object.assign(next._context.directives,app._context.directives);",
40
+ "Object.assign(next._context.provides,app._context.provides);",
41
+ "next._context.mixins.push(...app._context.mixins);",
42
+ "}",
43
+ "window.__app__=next;",
44
+ `document.documentElement.classList.add("${PREVIEW_SCROLL_HTML_CLASS}");`,
45
+ "return next.mount(container,...rest)",
46
+ "}",
47
+ "})()"
48
+ ].join("");
49
+ var PREVIEW_SCROLLER_STYLE = [
50
+ `html.${PREVIEW_SCROLL_HTML_CLASS},`,
51
+ `html.${PREVIEW_SCROLL_HTML_CLASS} body,`,
52
+ `html.${PREVIEW_SCROLL_HTML_CLASS} #app{`,
53
+ "height:100%;min-height:100%;margin:0;overflow:hidden",
54
+ "}",
55
+ `html.${PREVIEW_SCROLL_HTML_CLASS} #app>.vc-scroller{`,
56
+ "height:100%;max-height:100%;box-sizing:border-box",
57
+ "}"
58
+ ].join("");
59
+ var resolveSandboxContainer = (sandbox) => sandbox?.container ? unref(sandbox.container) : null;
60
+ var resolveSandboxMessageSource = (sandbox) => {
61
+ const exposed = sandbox?.messageSource ? unref(sandbox.messageSource) : null;
62
+ if (exposed && typeof exposed.postMessage === "function") return exposed;
63
+ return (resolveSandboxContainer(sandbox)?.querySelector("iframe"))?.contentWindow ?? null;
64
+ };
65
+ /**
66
+ * iframe 的 contentWindow 在 jsdom 里可能是 null,仍要和旧的元素校验一致。
67
+ * 直接渲染会暴露真实的 messageSource。
68
+ * @param sandbox
69
+ * @param event
70
+ */
71
+ var isSandboxMessage = (sandbox, event) => {
72
+ const explicit = sandbox?.messageSource ? unref(sandbox.messageSource) : null;
73
+ if (explicit && typeof explicit.postMessage === "function") return event.source === explicit;
74
+ const iframe = resolveSandboxContainer(sandbox)?.querySelector("iframe");
75
+ return !!iframe && event.source === iframe.contentWindow;
76
+ };
77
+ var resolveScrollContent = (iframeDocument) => {
78
+ if (!iframeDocument.documentElement.classList.contains("docs-playground-preview-scroll")) return null;
79
+ return iframeDocument.querySelector(`.${PREVIEW_SCROLL_CONTENT_CLASS}`);
80
+ };
81
+ var useSandboxAutoHeight = (sandboxRef) => {
82
+ const height = ref(24);
83
+ let container = null;
84
+ let iframe = null;
85
+ let containerObserver = null;
86
+ let contentObserver = null;
87
+ let scrollMountObserver = null;
88
+ let observedScrollNode = null;
89
+ let frameId = 0;
90
+ const cancelMeasure = () => {
91
+ if (!frameId) return;
92
+ cancelAnimationFrame(frameId);
93
+ frameId = 0;
94
+ };
95
+ const measureDocumentContent = (iframeWindow, iframeDocument) => {
96
+ const { body, documentElement } = iframeDocument;
97
+ const bodyStyle = iframeWindow.getComputedStyle(body);
98
+ const bodyMargins = (Number.parseFloat(bodyStyle.marginTop) || 0) + (Number.parseFloat(bodyStyle.marginBottom) || 0);
99
+ const rootTop = documentElement.getBoundingClientRect().top;
100
+ const bodyRect = body.getBoundingClientRect();
101
+ const collapsedTop = Math.max(0, bodyRect.top - rootTop);
102
+ let childrenBottom = 0;
103
+ for (const child of body.children) {
104
+ if (iframeWindow.getComputedStyle(child).position === "fixed") continue;
105
+ const rect = child.getBoundingClientRect();
106
+ const marginBottom = Number.parseFloat(iframeWindow.getComputedStyle(child).marginBottom) || 0;
107
+ childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
108
+ }
109
+ const scrollHeight = Math.max(body.scrollHeight, documentElement.scrollHeight);
110
+ const overflowingHeight = scrollHeight > (iframe?.clientHeight || 0) + 1 ? scrollHeight : 0;
111
+ return Math.ceil(Math.max(body.offsetHeight + bodyMargins + collapsedTop, bodyRect.height + bodyMargins + collapsedTop, childrenBottom, overflowingHeight));
112
+ };
113
+ const measureScrollContent = (scrollContent) => {
114
+ const contentStyle = scrollContent.ownerDocument.defaultView?.getComputedStyle(scrollContent);
115
+ const margins = contentStyle ? (Number.parseFloat(contentStyle.marginTop) || 0) + (Number.parseFloat(contentStyle.marginBottom) || 0) : 0;
116
+ let childrenBottom = 0;
117
+ const rootTop = scrollContent.getBoundingClientRect().top;
118
+ for (const child of scrollContent.children) {
119
+ const rect = child.getBoundingClientRect();
120
+ const marginBottom = Number.parseFloat(scrollContent.ownerDocument.defaultView?.getComputedStyle(child).marginBottom || "0") || 0;
121
+ childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
122
+ }
123
+ return Math.ceil(Math.max(scrollContent.scrollHeight + margins, scrollContent.offsetHeight + margins, childrenBottom + margins));
124
+ };
125
+ const isPendingPreviewApp = (iframeDocument) => {
126
+ const app = iframeDocument.getElementById("app");
127
+ return app ? app.childElementCount === 0 : !!iframe?.srcdoc;
128
+ };
129
+ const releaseBootstrapHeight = (target) => {
130
+ nextTick(() => {
131
+ if (iframe === target) target.style.height = "100%";
132
+ });
133
+ };
134
+ const measureElementChildren = (root) => {
135
+ const view = root.ownerDocument.defaultView || window;
136
+ const rootTop = root.getBoundingClientRect().top;
137
+ let childrenBottom = 0;
138
+ for (const child of root.children) {
139
+ if (!(child instanceof HTMLElement)) continue;
140
+ if (view.getComputedStyle(child).position === "fixed") continue;
141
+ const rect = child.getBoundingClientRect();
142
+ const marginBottom = Number.parseFloat(view.getComputedStyle(child).marginBottom) || 0;
143
+ childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
144
+ }
145
+ const overflowingHeight = root.scrollHeight > root.clientHeight + 1 ? root.scrollHeight : 0;
146
+ return Math.ceil(Math.max(root.offsetHeight, childrenBottom, overflowingHeight));
147
+ };
148
+ const measureLocalContainer = () => {
149
+ if (!container) return;
150
+ const scrollContent = container.querySelector(`.${PREVIEW_SCROLL_CONTENT_CLASS}`);
151
+ const mount = container.querySelector(".docs-playground-local__mount");
152
+ const target = scrollContent || mount;
153
+ if (!target || !scrollContent && target.childElementCount === 0) return;
154
+ const contentHeight = scrollContent ? measureScrollContent(scrollContent) : measureElementChildren(target);
155
+ const nextHeight = Math.max(contentHeight, 24);
156
+ if (height.value !== nextHeight) height.value = nextHeight;
157
+ };
158
+ const isLocalContainer = (element) => element?.classList.contains("docs-playground-local") === true;
159
+ const measure = () => {
160
+ frameId = 0;
161
+ if (isLocalContainer(container)) {
162
+ measureLocalContainer();
163
+ return;
164
+ }
165
+ if (!iframe) return;
166
+ try {
167
+ const iframeWindow = iframe.contentWindow;
168
+ const iframeDocument = iframe.contentDocument;
169
+ if (!iframeWindow || !iframeDocument?.body) return;
170
+ const scrollContent = resolveScrollContent(iframeDocument);
171
+ if (!scrollContent && isPendingPreviewApp(iframeDocument)) return;
172
+ const contentHeight = scrollContent ? measureScrollContent(scrollContent) : measureDocumentContent(iframeWindow, iframeDocument);
173
+ const nextHeight = Math.max(contentHeight, 24);
174
+ if (height.value !== nextHeight) height.value = nextHeight;
175
+ releaseBootstrapHeight(iframe);
176
+ } catch {
177
+ if (height.value !== 24) height.value = 24;
178
+ }
179
+ };
180
+ const scheduleMeasure = () => {
181
+ if (frameId) return;
182
+ frameId = requestAnimationFrame(measure);
183
+ };
184
+ const disconnectContent = () => {
185
+ contentObserver?.disconnect();
186
+ contentObserver = null;
187
+ scrollMountObserver?.disconnect();
188
+ scrollMountObserver = null;
189
+ observedScrollNode = null;
190
+ cancelMeasure();
191
+ };
192
+ const observeContent = () => {
193
+ disconnectContent();
194
+ if (!iframe) return;
195
+ try {
196
+ const iframeWindow = iframe.contentWindow;
197
+ const iframeDocument = iframe.contentDocument;
198
+ const Observer = iframeWindow?.ResizeObserver;
199
+ if (!iframeWindow || !iframeDocument?.body || !Observer) {
200
+ scheduleMeasure();
201
+ return;
202
+ }
203
+ const observer = new Observer(scheduleMeasure);
204
+ observer.observe(iframeDocument.documentElement);
205
+ observer.observe(iframeDocument.body);
206
+ contentObserver = observer;
207
+ const attachScrollContent = (node) => {
208
+ if (!node || !contentObserver) return false;
209
+ if (observedScrollNode === node) return true;
210
+ if (observedScrollNode) contentObserver.unobserve(observedScrollNode);
211
+ observedScrollNode = node;
212
+ contentObserver.observe(node);
213
+ return true;
214
+ };
215
+ const syncScrollContent = () => {
216
+ attachScrollContent(resolveScrollContent(iframeDocument));
217
+ scheduleMeasure();
218
+ };
219
+ scrollMountObserver = new MutationObserver(syncScrollContent);
220
+ scrollMountObserver.observe(iframeDocument.documentElement, {
221
+ attributes: true,
222
+ attributeFilter: ["class"]
223
+ });
224
+ scrollMountObserver.observe(iframeDocument.body, {
225
+ childList: true,
226
+ subtree: true
227
+ });
228
+ syncScrollContent();
229
+ } catch {
230
+ if (height.value !== 24) height.value = 24;
231
+ }
232
+ };
233
+ const handleIframeLoad = () => observeContent();
234
+ const setIframe = (nextIframe) => {
235
+ if (iframe === nextIframe) return;
236
+ iframe?.removeEventListener("load", handleIframeLoad);
237
+ disconnectContent();
238
+ iframe = nextIframe;
239
+ if (!iframe) {
240
+ height.value = 24;
241
+ return;
242
+ }
243
+ iframe.addEventListener("load", handleIframeLoad);
244
+ iframe.style.height = `${Math.max(window.innerHeight, 24)}px`;
245
+ observeContent();
246
+ };
247
+ const observeLocal = () => {
248
+ disconnectContent();
249
+ if (!container || typeof ResizeObserver === "undefined") {
250
+ scheduleMeasure();
251
+ return;
252
+ }
253
+ const observer = new ResizeObserver(scheduleMeasure);
254
+ observer.observe(container);
255
+ const mount = container.querySelector(".docs-playground-local__mount");
256
+ if (mount) observer.observe(mount);
257
+ contentObserver = observer;
258
+ scrollMountObserver = new MutationObserver(() => {
259
+ const nextMount = container?.querySelector(".docs-playground-local__mount");
260
+ if (nextMount && contentObserver) {
261
+ contentObserver.observe(nextMount);
262
+ for (const child of nextMount.children) contentObserver.observe(child);
263
+ }
264
+ scheduleMeasure();
265
+ });
266
+ scrollMountObserver.observe(container, {
267
+ childList: true,
268
+ subtree: true
269
+ });
270
+ scheduleMeasure();
271
+ };
272
+ const syncIframe = () => {
273
+ if (isLocalContainer(container)) {
274
+ iframe?.removeEventListener("load", handleIframeLoad);
275
+ iframe = null;
276
+ observeLocal();
277
+ return;
278
+ }
279
+ setIframe(container?.querySelector("iframe") || null);
280
+ };
281
+ const setContainer = (nextContainer) => {
282
+ if (container === nextContainer) return;
283
+ containerObserver?.disconnect();
284
+ containerObserver = null;
285
+ disconnectContent();
286
+ iframe?.removeEventListener("load", handleIframeLoad);
287
+ iframe = null;
288
+ container = nextContainer;
289
+ if (!container) {
290
+ height.value = 24;
291
+ return;
292
+ }
293
+ if (isLocalContainer(container)) {
294
+ observeLocal();
295
+ return;
296
+ }
297
+ containerObserver = new MutationObserver(syncIframe);
298
+ containerObserver.observe(container, {
299
+ childList: true,
300
+ subtree: true
301
+ });
302
+ syncIframe();
303
+ };
304
+ watch(() => resolveSandboxContainer(sandboxRef.value), setContainer, {
305
+ flush: "post",
306
+ immediate: true
307
+ });
308
+ onBeforeUnmount(() => {
309
+ setContainer(null);
310
+ cancelMeasure();
311
+ });
312
+ return height;
313
+ };
314
+ //#endregion
315
+ //#region packages/playground/src/core/runtime/error-guard.ts
316
+ var UNKNOWN_RUNTIME_ERROR = "运行时发生未知错误";
317
+ var MODULE_SPECIFIER_RE = /Failed to resolve module specifier|Error resolving module specifier/u;
318
+ var forwardSandboxRuntimeError = (event) => {
319
+ if (!(event instanceof ErrorEvent) || event.error != null || !event.message) return;
320
+ event.stopImmediatePropagation();
321
+ if (event.message === "ResizeObserver loop limit exceeded" || event.message === "ResizeObserver loop completed with undelivered notifications.") return;
322
+ window.parent.postMessage({
323
+ action: "error",
324
+ value: event.message
325
+ }, "*");
326
+ };
327
+ var SANDBOX_RUNTIME_ERROR_CAPTURE_HTML = [
328
+ "<script>",
329
+ `window.addEventListener('error', ${forwardSandboxRuntimeError.toString()}, true);`,
330
+ "<\/script>"
331
+ ].join("\n");
332
+ var toRuntimeErrorMessage = (value, message) => {
333
+ if (typeof value === "string" && value) return value;
334
+ if (value instanceof Error && value.message) return value.message;
335
+ if (value && typeof value === "object" && "message" in value) {
336
+ const valueMessage = value.message;
337
+ if (typeof valueMessage === "string" && valueMessage) return valueMessage;
338
+ }
339
+ if (typeof message === "string" && message) return message;
340
+ if (value !== null && value !== void 0) try {
341
+ return String(value);
342
+ } catch {
343
+ return UNKNOWN_RUNTIME_ERROR;
344
+ }
345
+ return UNKNOWN_RUNTIME_ERROR;
346
+ };
347
+ var normalizeRuntimeErrorMessage = (data) => {
348
+ if (!data || typeof data !== "object") return false;
349
+ const message = data;
350
+ if (message.action !== "error") return false;
351
+ message.value = toRuntimeErrorMessage(message.value, message.message);
352
+ return true;
353
+ };
354
+ var toErrorText = (value) => {
355
+ if (typeof value === "string" && value) return value;
356
+ if (value instanceof Error && value.message) return value.message;
357
+ return "";
358
+ };
359
+ var formatSandboxRuntimeError = (message, importMapTip) => {
360
+ if (!MODULE_SPECIFIER_RE.test(message)) return message;
361
+ return `${message.replace(/\. Relative references must.*$/u, "").replace(/\.$/u, "")}.\n${importMapTip}`;
362
+ };
363
+ var useSandboxRuntimeErrorGuard = (sandboxRef) => {
364
+ const error = ref("");
365
+ if (typeof window === "undefined") return error;
366
+ const handleMessage = (event) => {
367
+ const source = resolveSandboxMessageSource(sandboxRef.value);
368
+ if (!source || event.source !== source) return;
369
+ if (!normalizeRuntimeErrorMessage(event.data)) return;
370
+ error.value = event.data.value;
371
+ };
372
+ window.addEventListener("message", handleMessage, true);
373
+ onBeforeUnmount(() => window.removeEventListener("message", handleMessage, true));
374
+ return error;
375
+ };
376
+ //#endregion
377
+ //#region packages/playground/src/import-map.ts
378
+ /**
379
+ * 站点级同步内存:`$docs.modules` / `$docs.styles` 与管理页 import、CSS 覆盖。
380
+ */
381
+ var normalize = (value) => value.trim();
382
+ var isSafePlaygroundHref = (href) => {
383
+ const next = href.trim();
384
+ if (!next) return false;
385
+ if (/^https?:\/\//iu.test(next)) return true;
386
+ return next.startsWith("/") && !next.startsWith("//");
387
+ };
388
+ var filterSafeHrefs = (value = {}) => Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string" && isSafePlaygroundHref(entry[1])));
389
+ var sanitizeMap = (value = {}) => filterSafeHrefs(Object.fromEntries(Object.entries(value).map(([alias, url]) => [normalize(alias), normalize(url)]).filter(([alias]) => Boolean(alias))));
390
+ var createMapStore = () => {
391
+ let data = {};
392
+ const snapshot = () => ({ ...data });
393
+ const drop = (alias) => {
394
+ if (data[alias] === void 0) return snapshot();
395
+ const next = { ...data };
396
+ delete next[alias];
397
+ data = next;
398
+ return snapshot();
399
+ };
400
+ return {
401
+ get: snapshot,
402
+ set: (value = {}) => {
403
+ data = sanitizeMap(value);
404
+ },
405
+ apply: (alias, url) => {
406
+ const nextAlias = normalize(alias);
407
+ const nextUrl = normalize(url);
408
+ if (!nextAlias) return snapshot();
409
+ if (!isSafePlaygroundHref(nextUrl)) return drop(nextAlias);
410
+ data = {
411
+ ...data,
412
+ [nextAlias]: nextUrl
413
+ };
414
+ return snapshot();
415
+ },
416
+ remove: (alias) => {
417
+ const nextAlias = normalize(alias);
418
+ return nextAlias ? drop(nextAlias) : snapshot();
419
+ }
420
+ };
421
+ };
422
+ var siteModules = createMapStore();
423
+ var siteStyles = createMapStore();
424
+ var importOverrides = createMapStore();
425
+ var styleOverrides = createMapStore();
426
+ /**
427
+ * 站点默认 import。合并时夹在内置 CDN 与实例 `builtinImportMap` 之间。
428
+ */
429
+ var getPlaygroundSiteModules = siteModules.get;
430
+ var setPlaygroundSiteModules = siteModules.set;
431
+ /**
432
+ * 站点默认预览 CSS。合并时夹在内置样式表与管理页覆盖之间。
433
+ */
434
+ var getPlaygroundSiteStyles = siteStyles.get;
435
+ var setPlaygroundSiteStyles = siteStyles.set;
436
+ var getPlaygroundImportMapOverrides = importOverrides.get;
437
+ var setPlaygroundImportMapOverrides = importOverrides.set;
438
+ var applyPlaygroundImportMapOverride = importOverrides.apply;
439
+ var removePlaygroundImportMapOverride = importOverrides.remove;
440
+ var getPlaygroundStyleOverrides = styleOverrides.get;
441
+ var setPlaygroundStyleOverrides = styleOverrides.set;
442
+ var applyPlaygroundStyleOverride = styleOverrides.apply;
443
+ var removePlaygroundStyleOverride = styleOverrides.remove;
444
+ var clearPlaygroundMaps = () => {
445
+ [
446
+ siteModules,
447
+ siteStyles,
448
+ importOverrides,
449
+ styleOverrides
450
+ ].forEach((store) => store.set());
451
+ };
452
+ //#endregion
453
+ //#region packages/playground/src/preview-service.ts
454
+ /** Preview iframe 内的高度服务;class 会被序列化到 iframe 执行。 */
455
+ var PlaygroundService = class {
456
+ target;
457
+ vue;
458
+ session;
459
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
460
+ sequence = 0;
461
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
462
+ disposed = false;
463
+ manual;
464
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
465
+ requests = /* @__PURE__ */ new Set();
466
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
467
+ owners = /* @__PURE__ */ new Set();
468
+ /**
469
+ * @param target 当前预览窗口。
470
+ * @param vue 与预览应用共用的 Vue 实例。
471
+ */
472
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
473
+ constructor(target, vue) {
474
+ this.target = target;
475
+ this.vue = vue;
476
+ this.session = `${Date.now()}-${Math.random()}`;
477
+ this.dispose = this.dispose.bind(this);
478
+ this.post("start");
479
+ this.target.addEventListener("pagehide", this.dispose);
480
+ }
481
+ /**
482
+ * 申请手动临时高度;新请求会替换上一个手动请求。
483
+ * @param height iframe 最小高度。
484
+ * @returns iframe 是否在超时前应用目标高度。
485
+ */
486
+ enable(height) {
487
+ this.validateHeight(height);
488
+ this.manual?.release();
489
+ this.manual = this.createRequest(height);
490
+ return this.manual.ready;
491
+ }
492
+ /** 释放手动临时高度,不影响 `run` 创建的请求。 */
493
+ disable() {
494
+ this.manual?.release();
495
+ this.manual = void 0;
496
+ }
497
+ /**
498
+ * 创建等待高度后执行的事件处理函数。
499
+ * @param height iframe 最小高度。
500
+ * @param optionsOrHandler 可见状态配置,或最后一个 handler。
501
+ * @param lastHandler 使用配置时的最后一个 handler。
502
+ * @returns 保留 handler 参数和结果的异步事件处理函数。
503
+ */
504
+ run(height, optionsOrHandler, lastHandler) {
505
+ this.validateHeight(height);
506
+ if (optionsOrHandler === void 0) return (visible) => {
507
+ if (visible) return this.enable(height);
508
+ this.disable();
509
+ return Promise.resolve(void 0);
510
+ };
511
+ const visible = (typeof optionsOrHandler === "function" ? {} : optionsOrHandler).visible;
512
+ if (visible !== void 0 && !this.vue.isRef(visible) && typeof visible !== "function") throw new TypeError("Playground visible must be a ref or getter");
513
+ const handler = typeof optionsOrHandler === "function" ? optionsOrHandler : lastHandler ?? (this.vue.isRef(visible) ? () => {
514
+ visible.value = true;
515
+ } : void 0);
516
+ if (typeof handler !== "function") throw new TypeError("Playground run requires a handler");
517
+ let ownerDisposed = false;
518
+ let current;
519
+ let pending;
520
+ const disposeOwner = () => {
521
+ ownerDisposed = true;
522
+ current?.release();
523
+ this.owners.delete(disposeOwner);
524
+ };
525
+ this.owners.add(disposeOwner);
526
+ if (this.vue.getCurrentScope()) this.vue.onScopeDispose(disposeOwner);
527
+ return (...args) => {
528
+ if (this.disposed || ownerDisposed) return Promise.resolve(void 0);
529
+ if (pending) return pending;
530
+ const request = current = this.createRequest(height);
531
+ let executionFinished = false;
532
+ const unlock = () => {
533
+ if (executionFinished && request.released && current === request) {
534
+ pending = void 0;
535
+ current = void 0;
536
+ }
537
+ };
538
+ request.closed.then(unlock);
539
+ const execution = (async () => {
540
+ try {
541
+ if (!await request.ready || ownerDisposed || this.disposed) return void 0;
542
+ if (visible) request.watch(visible);
543
+ const result = await handler(...args);
544
+ if (!visible || !this.vue.toValue(visible)) request.release();
545
+ return result;
546
+ } catch (error) {
547
+ request.release();
548
+ throw error;
549
+ } finally {
550
+ executionFinished = true;
551
+ unlock();
552
+ }
553
+ })();
554
+ pending = execution;
555
+ return execution;
556
+ };
557
+ }
558
+ /** 释放当前预览应用持有的全部高度和监听。 */
559
+ /* istanbul ignore next -- 注入测试会从序列化后的 iframe class 调用该方法。 */
560
+ dispose() {
561
+ if (this.disposed) return;
562
+ this.disposed = true;
563
+ for (const disposeOwner of this.owners) disposeOwner();
564
+ for (const request of this.requests) request.release();
565
+ this.post("stop");
566
+ this.target.removeEventListener("pagehide", this.dispose);
567
+ if (this.target.$docsPlayground === this) delete this.target.$docsPlayground;
568
+ }
569
+ /** @param height iframe 最小高度。 */
570
+ validateHeight(height) {
571
+ if (!Number.isFinite(height) || height <= 0) throw new TypeError("Playground height must be a positive finite number");
572
+ }
573
+ /**
574
+ * @param operation 请求操作。
575
+ * @param id 请求标识。
576
+ * @param height iframe 最小高度。
577
+ */
578
+ /* istanbul ignore next -- 构造和销毁时会从序列化后的 iframe class 调用该方法。 */
579
+ post(operation, id = 0, height = 0) {
580
+ this.target.parent.postMessage({
581
+ action: "docs:height",
582
+ session: this.session,
583
+ operation,
584
+ id,
585
+ height
586
+ }, "*");
587
+ }
588
+ /**
589
+ * @param height iframe 最小高度。
590
+ * @returns 内部等待与释放句柄。
591
+ */
592
+ createRequest(height) {
593
+ this.validateHeight(height);
594
+ const id = ++this.sequence;
595
+ let released = false;
596
+ let settled = false;
597
+ let frame = 0;
598
+ let timer = 0;
599
+ let stopWatch;
600
+ let resolveReady;
601
+ let resolveClosed;
602
+ const ready = new Promise((resolve) => {
603
+ resolveReady = resolve;
604
+ });
605
+ const closed = new Promise((resolve) => {
606
+ resolveClosed = resolve;
607
+ });
608
+ const stopWaiting = () => {
609
+ this.target.cancelAnimationFrame(frame);
610
+ if (timer) {
611
+ this.target.clearTimeout(timer);
612
+ timer = 0;
613
+ }
614
+ this.target.removeEventListener("message", onMessage);
615
+ };
616
+ const release = () => {
617
+ if (released) return;
618
+ released = true;
619
+ stopWaiting();
620
+ stopWatch?.();
621
+ resolveReady(false);
622
+ resolveClosed();
623
+ this.requests.delete(request);
624
+ this.post("release", id);
625
+ };
626
+ const check = () => {
627
+ if (released) return;
628
+ if (this.target.innerHeight >= height) {
629
+ settled = true;
630
+ stopWaiting();
631
+ resolveReady(true);
632
+ } else frame = this.target.requestAnimationFrame(check);
633
+ };
634
+ const onMessage = (event) => {
635
+ const data = event.data;
636
+ if (event.source !== this.target.parent || data?.action !== "docs:height:applied" || data.session !== this.session || data.id !== id || settled) return;
637
+ if (!data.accepted) release();
638
+ else check();
639
+ };
640
+ const request = {
641
+ ready,
642
+ closed,
643
+ get released() {
644
+ return released;
645
+ },
646
+ release,
647
+ watch: (visible) => {
648
+ stopWatch = this.vue.watch(() => this.vue.toValue(visible), (value) => {
649
+ if (!value) release();
650
+ }, { flush: "sync" });
651
+ }
652
+ };
653
+ this.requests.add(request);
654
+ if (this.disposed) release();
655
+ else {
656
+ this.target.addEventListener("message", onMessage);
657
+ timer = this.target.setTimeout(() => {
658
+ timer = 0;
659
+ release();
660
+ }, 2e3);
661
+ this.post("enable", id, height);
662
+ }
663
+ return request;
664
+ }
665
+ };
666
+ var PREVIEW_SERVICE_IMPORT_CODE = "import * as __docsPreviewVue from \"vue\"";
667
+ var PREVIEW_SERVICE_USE_CODE = [
668
+ `;const __docsPlayground=new (${PlaygroundService.toString()})(window,__docsPreviewVue);`,
669
+ "app.provide(\"docs:playground\",__docsPlayground);",
670
+ "window.$docsPlayground=__docsPlayground;",
671
+ "app.mixin({beforeUnmount(){if(this===this.$root)__docsPlayground.dispose()}});"
672
+ ].join("");
673
+ //#endregion
674
+ //#region packages/playground/src/core/store.ts
675
+ var DOCS_LINK_IMPORT_CODE = "import { h as __docsH } from \"vue\"";
676
+ var DOCS_LINK_USE_CODE = [
677
+ PREVIEW_SCROLL_RESET_CODE,
678
+ "app.component(\"DocsLink\",{",
679
+ "props:{to:{type:String,default:\"\"}},",
680
+ "setup(props,{slots}){return()=>__docsH(\"a\",{",
681
+ "href:props.to,",
682
+ "style:{color:\"inherit\",textDecoration:\"none\"},",
683
+ "onClick(event){event.preventDefault();window.parent.postMessage({action:\"docs:navigate\",to:props.to},\"*\")}",
684
+ "},slots.default?.())}",
685
+ "})"
686
+ ].join("");
687
+ var escapeHtmlAttr = (value) => value.replace(/&/gu, "&amp;").replace(/"/gu, "&quot;").replace(/'/gu, "&#39;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;");
688
+ /**
689
+ * runtime 画布色。`previewInset` 露出的 padding、iframe chrome 和 sandbox
690
+ * `html`/`body` 必须共用这一变量,暗色下 `--docs-background-color` 与
691
+ * `--vc-background-color-light` 不同,否则会出现色带割裂。
692
+ */
693
+ var PLAYGROUND_RUNTIME_CANVAS_BACKGROUND = "var(--vc-background-color-light, var(--docs-background-color, #fff))";
694
+ var createRuntimePreviewOptions = (cdnURL = DEFAULT_CDN_URL, options = {}) => {
695
+ const previewScroller = options.previewScroller === true;
696
+ const hrefs = Object.values({
697
+ ...createBuiltinStyles(cdnURL),
698
+ ...getPlaygroundSiteStyles(),
699
+ ...getPlaygroundStyleOverrides()
700
+ });
701
+ const styleBlocks = [
702
+ `html,body{height:auto;min-height:0;background:${PLAYGROUND_RUNTIME_CANVAS_BACKGROUND}}`,
703
+ "body{color:var(--vc-foreground-color,#080f20)}",
704
+ ...previewScroller ? [PREVIEW_SCROLLER_STYLE] : []
705
+ ];
706
+ return {
707
+ showRuntimeError: false,
708
+ showRuntimeWarning: false,
709
+ headHTML: [
710
+ SANDBOX_RUNTIME_ERROR_CAPTURE_HTML,
711
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
712
+ ...hrefs.map((href) => `<link rel="stylesheet" href="${escapeHtmlAttr(href)}">`),
713
+ "<style>",
714
+ ...styleBlocks,
715
+ "</style>"
716
+ ].join("\n"),
717
+ customCode: {
718
+ importCode: `${DOCS_LINK_IMPORT_CODE}\n${PREVIEW_SERVICE_IMPORT_CODE}`,
719
+ useCode: `${DOCS_LINK_USE_CODE}\n${PREVIEW_SERVICE_USE_CODE}`
720
+ }
721
+ };
722
+ };
723
+ var toReplFilename = (filename) => filename.startsWith("src/") ? filename : `src/${filename}`;
724
+ var createReplFile = (filename, code) => {
725
+ const replFilename = toReplFilename(filename);
726
+ return new File(replFilename, code);
727
+ };
728
+ /**
729
+ * 默认 CDN → 站点 modules → 实例 builtinImportMap → 管理页覆盖。
730
+ * iframe import map 与直接渲染加载的是同一份地址表。
731
+ * @param cdnURL
732
+ * @param builtinImportMap
733
+ */
734
+ var createRuntimeImports = (cdnURL = DEFAULT_CDN_URL, builtinImportMap) => ({
735
+ ...createBuiltinImports(cdnURL),
736
+ ...getPlaygroundSiteModules(),
737
+ ...filterSafeHrefs(builtinImportMap?.imports),
738
+ ...getPlaygroundImportMapOverrides()
739
+ });
740
+ var createRuntimeStore = (files, entry, options) => {
741
+ const { cdnURL, builtinImportMap, ...storeOptions } = options;
742
+ const replFiles = ref(Object.fromEntries(Object.entries(files).map(([filename, code]) => {
743
+ const file = createReplFile(filename, code);
744
+ return [file.filename, file];
745
+ })));
746
+ const mainFile = ref(toReplFilename(entry));
747
+ const activeFilename = ref(toReplFilename(entry));
748
+ const store = useStore({
749
+ ...storeOptions,
750
+ files: replFiles,
751
+ mainFile,
752
+ activeFilename,
753
+ builtinImportMap: ref({
754
+ ...builtinImportMap,
755
+ imports: createRuntimeImports(cdnURL, builtinImportMap)
756
+ }),
757
+ template: ref({
758
+ welcomeSFC: files[entry],
759
+ newSFC: NEW_SFC_CODE
760
+ })
761
+ });
762
+ bindPlaygroundScss(store);
763
+ whenSassReady(files, () => store.init());
764
+ return store;
765
+ };
766
+ //#endregion
767
+ export { PREVIEW_SCROLLER_USE_CODE as A, toErrorText as C, resolveSandboxMessageSource as D, resolveSandboxContainer as E, PREVIEW_SCROLL_HTML_CLASS as M, PREVIEW_SCROLL_RESET_CODE as N, useSandboxAutoHeight as O, formatSandboxRuntimeError as S, isSandboxMessage as T, removePlaygroundStyleOverride as _, createRuntimeStore as a, setPlaygroundSiteStyles as b, applyPlaygroundImportMapOverride as c, getPlaygroundImportMapOverrides as d, getPlaygroundSiteModules as f, removePlaygroundImportMapOverride as g, isSafePlaygroundHref as h, createRuntimePreviewOptions as i, PREVIEW_SCROLL_CONTENT_CLASS as j, PREVIEW_SCROLLER_IMPORT_CODE as k, applyPlaygroundStyleOverride as l, getPlaygroundStyleOverrides as m, createReplFile as n, toReplFilename as o, getPlaygroundSiteStyles as p, createRuntimeImports as r, PlaygroundService as s, PLAYGROUND_RUNTIME_CANVAS_BACKGROUND as t, clearPlaygroundMaps as u, setPlaygroundImportMapOverrides as v, useSandboxRuntimeErrorGuard as w, setPlaygroundStyleOverrides as x, setPlaygroundSiteModules as y };