@deot/docs-playground 1.1.1 → 1.1.2
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/README.md +60 -0
- package/dist/index.cjs +324 -13
- package/dist/index.d.ts +36 -0
- package/dist/index.js +324 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,66 @@ const files = ref({
|
|
|
70
70
|
|
|
71
71
|
Playground 只接受来自自身 iframe 的导航消息。宿主应用应监听 `navigate` 并交给自己的 Router 处理。
|
|
72
72
|
|
|
73
|
+
## 预览内的临时高度
|
|
74
|
+
|
|
75
|
+
Playground 会在预览应用中注入 `docs:playground`,也可通过 iframe 内的 `window.$docsPlayground` 访问同一服务。适用于 ActionSheet、Modal、Drawer、Message 等依赖视口高度的示例,无需自行撑高 DOM 或轮询 `window.innerHeight`。
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { inject, ref } from 'vue';
|
|
79
|
+
import type { PlaygroundService } from '@deot/docs-playground';
|
|
80
|
+
|
|
81
|
+
const playground = inject<PlaygroundService>('docs:playground')!;
|
|
82
|
+
const visible = ref(false);
|
|
83
|
+
|
|
84
|
+
// 定义时不执行,点击后先等待高度就绪,再打开弹层;visible 关闭时恢复。
|
|
85
|
+
// visible 是 ref 且省略 handler 时,默认执行 visible.value = true。
|
|
86
|
+
const handleOpen = playground.run(560, { visible });
|
|
87
|
+
|
|
88
|
+
// 也可生成 boolean 状态处理函数:true 扩展高度,false 恢复高度。
|
|
89
|
+
const handleVisibleChange = playground.run(560);
|
|
90
|
+
|
|
91
|
+
// 支持 getter 和 computed;不能传入普通 boolean 快照。
|
|
92
|
+
const openDrawer = playground.run(560, { visible: () => state.drawerVisible }, () => {
|
|
93
|
+
state.drawerVisible = true;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 回调返回的 Promise 表示弹层关闭时,可省略 options。
|
|
97
|
+
const openActionSheet = playground.run(560, async () => {
|
|
98
|
+
await MActionSheet.open({ title: '请选择操作' });
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`run(height)`、`run(height, handler)` 和 `run(height, options, handler)` 均返回事件处理函数。传入 handler 时会透传调用参数和回调结果,最后一个参数始终为 handler。`options` 当前只包含 `visible?: Ref<boolean> | (() => boolean)`;传入可写 `Ref<boolean>` 时可以省略 handler,默认将其设为 `true`。getter 和只读 computed 需要显式 handler。示例中的 `state`、`MActionSheet` 由业务代码提供。
|
|
103
|
+
|
|
104
|
+
| 接口 | 行为 |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `enable(height): Promise<boolean>` | 设置手动临时高度,等待 iframe 实际高度达到要求后返回 `true`。连续调用替换旧的手动请求。 |
|
|
107
|
+
| `disable(): void` | 释放手动请求;重复调用无副作用,不影响 `run` 的请求。 |
|
|
108
|
+
| `run(height)` | 返回 boolean 事件处理函数;收到 `true` 时调用 `enable(height)`,收到 `false` 时调用 `disable()`。 |
|
|
109
|
+
| `run(height, handler)` | 等待高度后执行;同步返回或 Promise 完成时恢复高度,异常也恢复并继续抛出。 |
|
|
110
|
+
| `run(height, { visible: ref })` | 等待高度后默认执行 `visible.value = true`,并在状态关闭时恢复高度。 |
|
|
111
|
+
| `run(height, { visible }, handler)` | 执行前监听状态,关闭时恢复;回调完成后未打开或回调抛错时也释放。异步打开流程应由回调返回的 Promise 覆盖。 |
|
|
112
|
+
|
|
113
|
+
同一个 `run` 返回函数在回调与弹层均结束前会复用当前 Promise,避免重复点击打开;不同函数的请求独立管理并取最大高度。Message / Toast 等立即返回的 API 应通过 `visible` 或关闭回调恢复高度,不能把函数返回当成弹层关闭。
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
const handleMessage = async () => {
|
|
117
|
+
if (!await playground.enable(240)) return;
|
|
118
|
+
try {
|
|
119
|
+
Message.info({ content: '已保存', onClose: () => playground.disable() });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
playground.disable();
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
高度为正有限 CSS px,表示 iframe 内部可视高度,不包含 `previewInset` 与边框。普通预览取原有高度与临时请求的最大值,释放后恢复当前视口规则;独立窗口受可用屏幕空间限制。等待超时(2 秒)、取消或空间不足时返回 `false`;`run` 在这些情况下不执行回调并返回 `undefined`。
|
|
128
|
+
|
|
129
|
+
在 `setup` 中创建的 `run` 会随当前 Vue scope 自动取消等待、移除监听并释放高度;其他位置创建时绑定预览应用。手动 `enable/disable` 绑定整个预览应用,子组件单独卸载时按需自行 `disable()`。刷新、重新运行及应用卸载会统一释放请求。服务不取消已经启动的业务任务,也不调用组件的 `destroy()`;示例保留必要的业务 `onUnmounted`,只需删除高度等待使用的 `disposed` 标记。
|
|
130
|
+
|
|
131
|
+
类型导出为 `PlaygroundService`、`PlaygroundRunOptions` 和 `PlaygroundWindow`。Window 类型可通过 `(window as PlaygroundWindow).$docsPlayground` 显式使用;服务仅存在于 Playground 预览应用,不向普通页面声明必然存在的全局变量。接入项目需使用包含该接口的新版 Playground。
|
|
132
|
+
|
|
73
133
|
## 其他公共导出
|
|
74
134
|
|
|
75
135
|
| 导出 | 说明 |
|
package/dist/index.cjs
CHANGED
|
@@ -896,6 +896,8 @@ var PREVIEW_SCROLLER_USE_CODE = [
|
|
|
896
896
|
"if(app._context&&next._context){",
|
|
897
897
|
"if(app._context.components)Object.assign(next._context.components,app._context.components);",
|
|
898
898
|
"if(app._context.directives)Object.assign(next._context.directives,app._context.directives);",
|
|
899
|
+
"Object.assign(next._context.provides,app._context.provides);",
|
|
900
|
+
"next._context.mixins.push(...app._context.mixins);",
|
|
899
901
|
"}",
|
|
900
902
|
"window.__app__=next;",
|
|
901
903
|
`document.documentElement.classList.add("${PREVIEW_SCROLL_HTML_CLASS}");`,
|
|
@@ -942,6 +944,7 @@ var useSandboxAutoHeight = (sandboxRef) => {
|
|
|
942
944
|
const collapsedTop = Math.max(0, bodyRect.top - rootTop);
|
|
943
945
|
let childrenBottom = 0;
|
|
944
946
|
for (const child of body.children) {
|
|
947
|
+
if (iframeWindow.getComputedStyle(child).position === "fixed") continue;
|
|
945
948
|
const rect = child.getBoundingClientRect();
|
|
946
949
|
const marginBottom = Number.parseFloat(iframeWindow.getComputedStyle(child).marginBottom) || 0;
|
|
947
950
|
childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
|
|
@@ -962,9 +965,14 @@ var useSandboxAutoHeight = (sandboxRef) => {
|
|
|
962
965
|
}
|
|
963
966
|
return Math.ceil(Math.max(scrollContent.scrollHeight + margins, scrollContent.offsetHeight + margins, childrenBottom + margins));
|
|
964
967
|
};
|
|
965
|
-
const
|
|
968
|
+
const isPendingPreviewApp = (iframeDocument) => {
|
|
966
969
|
const app = iframeDocument.getElementById("app");
|
|
967
|
-
return
|
|
970
|
+
return app ? app.childElementCount === 0 : !!iframe?.srcdoc;
|
|
971
|
+
};
|
|
972
|
+
const releaseBootstrapHeight = (target) => {
|
|
973
|
+
(0, vue.nextTick)(() => {
|
|
974
|
+
if (iframe === target) target.style.height = "100%";
|
|
975
|
+
});
|
|
968
976
|
};
|
|
969
977
|
const measure = () => {
|
|
970
978
|
frameId = 0;
|
|
@@ -974,10 +982,11 @@ var useSandboxAutoHeight = (sandboxRef) => {
|
|
|
974
982
|
const iframeDocument = iframe.contentDocument;
|
|
975
983
|
if (!iframeWindow || !iframeDocument?.body) return;
|
|
976
984
|
const scrollContent = resolveScrollContent(iframeDocument);
|
|
977
|
-
if (!scrollContent &&
|
|
985
|
+
if (!scrollContent && isPendingPreviewApp(iframeDocument)) return;
|
|
978
986
|
const contentHeight = scrollContent ? measureScrollContent(scrollContent) : measureDocumentContent(iframeWindow, iframeDocument);
|
|
979
987
|
const nextHeight = Math.max(contentHeight, 24);
|
|
980
988
|
if (height.value !== nextHeight) height.value = nextHeight;
|
|
989
|
+
releaseBootstrapHeight(iframe);
|
|
981
990
|
} catch {
|
|
982
991
|
if (height.value !== 24) height.value = 24;
|
|
983
992
|
}
|
|
@@ -1046,7 +1055,7 @@ var useSandboxAutoHeight = (sandboxRef) => {
|
|
|
1046
1055
|
return;
|
|
1047
1056
|
}
|
|
1048
1057
|
iframe.addEventListener("load", handleIframeLoad);
|
|
1049
|
-
iframe.style.height =
|
|
1058
|
+
iframe.style.height = `${Math.max(window.innerHeight, 24)}px`;
|
|
1050
1059
|
observeContent();
|
|
1051
1060
|
};
|
|
1052
1061
|
const syncIframe = () => setIframe(container?.querySelector("iframe") || null);
|
|
@@ -1396,6 +1405,73 @@ var useSandboxTheme = (sandboxRef) => {
|
|
|
1396
1405
|
});
|
|
1397
1406
|
};
|
|
1398
1407
|
//#endregion
|
|
1408
|
+
//#region packages/playground/src/core/runtime/height-bridge.ts
|
|
1409
|
+
/**
|
|
1410
|
+
* 只接收本宿主当前 iframe 的高度请求,应用重评时切换会话。
|
|
1411
|
+
* @param sandbox 预览容器。
|
|
1412
|
+
* @param maxHeight 当前可用高度,普通预览默认不限制。
|
|
1413
|
+
* @returns 所有有效请求的最大高度。
|
|
1414
|
+
*/
|
|
1415
|
+
function usePreviewHeightBridge(sandbox, maxHeight = () => Infinity) {
|
|
1416
|
+
const height = (0, vue.ref)(0);
|
|
1417
|
+
const requests = /* @__PURE__ */ new Map();
|
|
1418
|
+
let source = null;
|
|
1419
|
+
let session = "";
|
|
1420
|
+
let disposed = false;
|
|
1421
|
+
const sync = () => {
|
|
1422
|
+
height.value = Math.max(0, ...requests.values());
|
|
1423
|
+
};
|
|
1424
|
+
const reset = () => {
|
|
1425
|
+
requests.clear();
|
|
1426
|
+
session = "";
|
|
1427
|
+
source = null;
|
|
1428
|
+
sync();
|
|
1429
|
+
};
|
|
1430
|
+
const handleMessage = async (event) => {
|
|
1431
|
+
const iframe = resolveSandboxContainer(sandbox.value)?.querySelector("iframe");
|
|
1432
|
+
const data = event.data;
|
|
1433
|
+
if (!iframe?.contentWindow || event.source !== iframe.contentWindow || data?.action !== "docs:height" || typeof data.session !== "string") return;
|
|
1434
|
+
if (data.operation === "start") {
|
|
1435
|
+
reset();
|
|
1436
|
+
source = iframe.contentWindow;
|
|
1437
|
+
session = data.session;
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
if (source !== iframe.contentWindow || session !== data.session) return;
|
|
1441
|
+
if (data.operation === "stop") {
|
|
1442
|
+
reset();
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
if (!Number.isSafeInteger(data.id) || data.id <= 0) return;
|
|
1446
|
+
if (data.operation === "release") {
|
|
1447
|
+
requests.delete(data.id);
|
|
1448
|
+
sync();
|
|
1449
|
+
return;
|
|
1450
|
+
}
|
|
1451
|
+
if (data.operation !== "enable" || !Number.isFinite(data.height) || data.height <= 0) return;
|
|
1452
|
+
const accepted = data.height <= maxHeight();
|
|
1453
|
+
if (accepted) requests.set(data.id, data.height);
|
|
1454
|
+
sync();
|
|
1455
|
+
const receiver = source;
|
|
1456
|
+
await (0, vue.nextTick)();
|
|
1457
|
+
if (disposed || source !== receiver || session !== data.session) return;
|
|
1458
|
+
receiver?.postMessage({
|
|
1459
|
+
action: "docs:height:applied",
|
|
1460
|
+
session,
|
|
1461
|
+
id: data.id,
|
|
1462
|
+
accepted
|
|
1463
|
+
}, "*");
|
|
1464
|
+
};
|
|
1465
|
+
(0, vue.watch)(() => resolveSandboxContainer(sandbox.value), reset, { flush: "sync" });
|
|
1466
|
+
if (typeof window !== "undefined") window.addEventListener("message", handleMessage);
|
|
1467
|
+
(0, vue.onBeforeUnmount)(() => {
|
|
1468
|
+
disposed = true;
|
|
1469
|
+
if (typeof window !== "undefined") window.removeEventListener("message", handleMessage);
|
|
1470
|
+
reset();
|
|
1471
|
+
});
|
|
1472
|
+
return height;
|
|
1473
|
+
}
|
|
1474
|
+
//#endregion
|
|
1399
1475
|
//#region packages/playground/src/core/runtime/toolbar.vue?vue&type=script&setup=true&lang.ts
|
|
1400
1476
|
var _hoisted_1$3 = { class: "docs-playground__tools" };
|
|
1401
1477
|
var _hoisted_2$3 = {
|
|
@@ -1640,7 +1716,9 @@ var BUILTIN_IMPORT_ASSETS = {
|
|
|
1640
1716
|
"@deot/helper": "@deot/helper/dist/index.js",
|
|
1641
1717
|
"normalize-wheel": "normalize-wheel-es/dist/index.mjs",
|
|
1642
1718
|
"photoswipe": "photoswipe/dist/photoswipe.esm.js",
|
|
1643
|
-
"photoswipe/lightbox": "photoswipe/dist/photoswipe-lightbox.esm.js"
|
|
1719
|
+
"photoswipe/lightbox": "photoswipe/dist/photoswipe-lightbox.esm.js",
|
|
1720
|
+
"vue": "vue/dist/vue.runtime.esm-browser.js",
|
|
1721
|
+
"vue/server-renderer": "vue/dist/server-renderer.esm-browser.js"
|
|
1644
1722
|
};
|
|
1645
1723
|
var normalizeCdnURL = (cdnURL = DEFAULT_CDN_URL) => {
|
|
1646
1724
|
return cdnURL.trim().replace(/\/+$/u, "") || "https://cdn.jsdelivr.net/npm";
|
|
@@ -1651,9 +1729,7 @@ var SASS_CDN_URL = jsdelivrEsmURL("sass/+esm");
|
|
|
1651
1729
|
var cdnAsset = (cdnURL, asset) => cdnJoin(normalizeCdnURL(cdnURL), asset);
|
|
1652
1730
|
var createBuiltinImports = (cdnURL = DEFAULT_CDN_URL) => ({
|
|
1653
1731
|
...Object.fromEntries(Object.entries(BUILTIN_IMPORT_ASSETS).map(([specifier, asset]) => [specifier, cdnAsset(cdnURL, asset)])),
|
|
1654
|
-
"lodash-es": jsdelivrEsmURL("lodash-es/+esm")
|
|
1655
|
-
"vue": "https://play.vuejs.org/vue.runtime.esm-browser.js",
|
|
1656
|
-
"vue/server-renderer": "https://play.vuejs.org/server-renderer.esm-browser.js"
|
|
1732
|
+
"lodash-es": jsdelivrEsmURL("lodash-es/+esm")
|
|
1657
1733
|
});
|
|
1658
1734
|
var createBuiltinStyles = (cdnURL = DEFAULT_CDN_URL) => Object.fromEntries(PREVIEW_STYLE_ASSETS.map((asset) => [asset, cdnAsset(cdnURL, asset)]));
|
|
1659
1735
|
//#endregion
|
|
@@ -1733,6 +1809,227 @@ var clearPlaygroundMaps = () => {
|
|
|
1733
1809
|
].forEach((store) => store.set());
|
|
1734
1810
|
};
|
|
1735
1811
|
//#endregion
|
|
1812
|
+
//#region packages/playground/src/preview-service.ts
|
|
1813
|
+
/** Preview iframe 内的高度服务;class 会被序列化到 iframe 执行。 */
|
|
1814
|
+
var PlaygroundService = class {
|
|
1815
|
+
target;
|
|
1816
|
+
vue;
|
|
1817
|
+
session;
|
|
1818
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1819
|
+
sequence = 0;
|
|
1820
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1821
|
+
disposed = false;
|
|
1822
|
+
manual;
|
|
1823
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1824
|
+
requests = /* @__PURE__ */ new Set();
|
|
1825
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1826
|
+
owners = /* @__PURE__ */ new Set();
|
|
1827
|
+
/**
|
|
1828
|
+
* @param target 当前预览窗口。
|
|
1829
|
+
* @param vue 与预览应用共用的 Vue 实例。
|
|
1830
|
+
*/
|
|
1831
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1832
|
+
constructor(target, vue) {
|
|
1833
|
+
this.target = target;
|
|
1834
|
+
this.vue = vue;
|
|
1835
|
+
this.session = `${Date.now()}-${Math.random()}`;
|
|
1836
|
+
this.dispose = this.dispose.bind(this);
|
|
1837
|
+
this.post("start");
|
|
1838
|
+
this.target.addEventListener("pagehide", this.dispose);
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* 申请手动临时高度;新请求会替换上一个手动请求。
|
|
1842
|
+
* @param height iframe 最小高度。
|
|
1843
|
+
* @returns iframe 是否在超时前应用目标高度。
|
|
1844
|
+
*/
|
|
1845
|
+
enable(height) {
|
|
1846
|
+
this.validateHeight(height);
|
|
1847
|
+
this.manual?.release();
|
|
1848
|
+
this.manual = this.createRequest(height);
|
|
1849
|
+
return this.manual.ready;
|
|
1850
|
+
}
|
|
1851
|
+
/** 释放手动临时高度,不影响 `run` 创建的请求。 */
|
|
1852
|
+
disable() {
|
|
1853
|
+
this.manual?.release();
|
|
1854
|
+
this.manual = void 0;
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* 创建等待高度后执行的事件处理函数。
|
|
1858
|
+
* @param height iframe 最小高度。
|
|
1859
|
+
* @param optionsOrHandler 可见状态配置,或最后一个 handler。
|
|
1860
|
+
* @param lastHandler 使用配置时的最后一个 handler。
|
|
1861
|
+
* @returns 保留 handler 参数和结果的异步事件处理函数。
|
|
1862
|
+
*/
|
|
1863
|
+
run(height, optionsOrHandler, lastHandler) {
|
|
1864
|
+
this.validateHeight(height);
|
|
1865
|
+
if (optionsOrHandler === void 0) return (visible) => {
|
|
1866
|
+
if (visible) return this.enable(height);
|
|
1867
|
+
this.disable();
|
|
1868
|
+
return Promise.resolve(void 0);
|
|
1869
|
+
};
|
|
1870
|
+
const visible = (typeof optionsOrHandler === "function" ? {} : optionsOrHandler).visible;
|
|
1871
|
+
if (visible !== void 0 && !this.vue.isRef(visible) && typeof visible !== "function") throw new TypeError("Playground visible must be a ref or getter");
|
|
1872
|
+
const handler = typeof optionsOrHandler === "function" ? optionsOrHandler : lastHandler ?? (this.vue.isRef(visible) ? () => {
|
|
1873
|
+
visible.value = true;
|
|
1874
|
+
} : void 0);
|
|
1875
|
+
if (typeof handler !== "function") throw new TypeError("Playground run requires a handler");
|
|
1876
|
+
let ownerDisposed = false;
|
|
1877
|
+
let current;
|
|
1878
|
+
let pending;
|
|
1879
|
+
const disposeOwner = () => {
|
|
1880
|
+
ownerDisposed = true;
|
|
1881
|
+
current?.release();
|
|
1882
|
+
this.owners.delete(disposeOwner);
|
|
1883
|
+
};
|
|
1884
|
+
this.owners.add(disposeOwner);
|
|
1885
|
+
if (this.vue.getCurrentScope()) this.vue.onScopeDispose(disposeOwner);
|
|
1886
|
+
return (...args) => {
|
|
1887
|
+
if (this.disposed || ownerDisposed) return Promise.resolve(void 0);
|
|
1888
|
+
if (pending) return pending;
|
|
1889
|
+
const request = current = this.createRequest(height);
|
|
1890
|
+
let executionFinished = false;
|
|
1891
|
+
const unlock = () => {
|
|
1892
|
+
if (executionFinished && request.released && current === request) {
|
|
1893
|
+
pending = void 0;
|
|
1894
|
+
current = void 0;
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
request.closed.then(unlock);
|
|
1898
|
+
const execution = (async () => {
|
|
1899
|
+
try {
|
|
1900
|
+
if (!await request.ready || ownerDisposed || this.disposed) return void 0;
|
|
1901
|
+
if (visible) request.watch(visible);
|
|
1902
|
+
const result = await handler(...args);
|
|
1903
|
+
if (!visible || !this.vue.toValue(visible)) request.release();
|
|
1904
|
+
return result;
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
request.release();
|
|
1907
|
+
throw error;
|
|
1908
|
+
} finally {
|
|
1909
|
+
executionFinished = true;
|
|
1910
|
+
unlock();
|
|
1911
|
+
}
|
|
1912
|
+
})();
|
|
1913
|
+
pending = execution;
|
|
1914
|
+
return execution;
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
/** 释放当前预览应用持有的全部高度和监听。 */
|
|
1918
|
+
/* istanbul ignore next -- 注入测试会从序列化后的 iframe class 调用该方法。 */
|
|
1919
|
+
dispose() {
|
|
1920
|
+
if (this.disposed) return;
|
|
1921
|
+
this.disposed = true;
|
|
1922
|
+
for (const disposeOwner of this.owners) disposeOwner();
|
|
1923
|
+
for (const request of this.requests) request.release();
|
|
1924
|
+
this.post("stop");
|
|
1925
|
+
this.target.removeEventListener("pagehide", this.dispose);
|
|
1926
|
+
if (this.target.$docsPlayground === this) delete this.target.$docsPlayground;
|
|
1927
|
+
}
|
|
1928
|
+
/** @param height iframe 最小高度。 */
|
|
1929
|
+
validateHeight(height) {
|
|
1930
|
+
if (!Number.isFinite(height) || height <= 0) throw new TypeError("Playground height must be a positive finite number");
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* @param operation 请求操作。
|
|
1934
|
+
* @param id 请求标识。
|
|
1935
|
+
* @param height iframe 最小高度。
|
|
1936
|
+
*/
|
|
1937
|
+
/* istanbul ignore next -- 构造和销毁时会从序列化后的 iframe class 调用该方法。 */
|
|
1938
|
+
post(operation, id = 0, height = 0) {
|
|
1939
|
+
this.target.parent.postMessage({
|
|
1940
|
+
action: "docs:height",
|
|
1941
|
+
session: this.session,
|
|
1942
|
+
operation,
|
|
1943
|
+
id,
|
|
1944
|
+
height
|
|
1945
|
+
}, "*");
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* @param height iframe 最小高度。
|
|
1949
|
+
* @returns 内部等待与释放句柄。
|
|
1950
|
+
*/
|
|
1951
|
+
createRequest(height) {
|
|
1952
|
+
this.validateHeight(height);
|
|
1953
|
+
const id = ++this.sequence;
|
|
1954
|
+
let released = false;
|
|
1955
|
+
let settled = false;
|
|
1956
|
+
let frame = 0;
|
|
1957
|
+
let timer = 0;
|
|
1958
|
+
let stopWatch;
|
|
1959
|
+
let resolveReady;
|
|
1960
|
+
let resolveClosed;
|
|
1961
|
+
const ready = new Promise((resolve) => {
|
|
1962
|
+
resolveReady = resolve;
|
|
1963
|
+
});
|
|
1964
|
+
const closed = new Promise((resolve) => {
|
|
1965
|
+
resolveClosed = resolve;
|
|
1966
|
+
});
|
|
1967
|
+
const stopWaiting = () => {
|
|
1968
|
+
this.target.cancelAnimationFrame(frame);
|
|
1969
|
+
if (timer) {
|
|
1970
|
+
this.target.clearTimeout(timer);
|
|
1971
|
+
timer = 0;
|
|
1972
|
+
}
|
|
1973
|
+
this.target.removeEventListener("message", onMessage);
|
|
1974
|
+
};
|
|
1975
|
+
const release = () => {
|
|
1976
|
+
if (released) return;
|
|
1977
|
+
released = true;
|
|
1978
|
+
stopWaiting();
|
|
1979
|
+
stopWatch?.();
|
|
1980
|
+
resolveReady(false);
|
|
1981
|
+
resolveClosed();
|
|
1982
|
+
this.requests.delete(request);
|
|
1983
|
+
this.post("release", id);
|
|
1984
|
+
};
|
|
1985
|
+
const check = () => {
|
|
1986
|
+
if (released) return;
|
|
1987
|
+
if (this.target.innerHeight >= height) {
|
|
1988
|
+
settled = true;
|
|
1989
|
+
stopWaiting();
|
|
1990
|
+
resolveReady(true);
|
|
1991
|
+
} else frame = this.target.requestAnimationFrame(check);
|
|
1992
|
+
};
|
|
1993
|
+
const onMessage = (event) => {
|
|
1994
|
+
const data = event.data;
|
|
1995
|
+
if (event.source !== this.target.parent || data?.action !== "docs:height:applied" || data.session !== this.session || data.id !== id || settled) return;
|
|
1996
|
+
if (!data.accepted) release();
|
|
1997
|
+
else check();
|
|
1998
|
+
};
|
|
1999
|
+
const request = {
|
|
2000
|
+
ready,
|
|
2001
|
+
closed,
|
|
2002
|
+
get released() {
|
|
2003
|
+
return released;
|
|
2004
|
+
},
|
|
2005
|
+
release,
|
|
2006
|
+
watch: (visible) => {
|
|
2007
|
+
stopWatch = this.vue.watch(() => this.vue.toValue(visible), (value) => {
|
|
2008
|
+
if (!value) release();
|
|
2009
|
+
}, { flush: "sync" });
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
this.requests.add(request);
|
|
2013
|
+
if (this.disposed) release();
|
|
2014
|
+
else {
|
|
2015
|
+
this.target.addEventListener("message", onMessage);
|
|
2016
|
+
timer = this.target.setTimeout(() => {
|
|
2017
|
+
timer = 0;
|
|
2018
|
+
release();
|
|
2019
|
+
}, 2e3);
|
|
2020
|
+
this.post("enable", id, height);
|
|
2021
|
+
}
|
|
2022
|
+
return request;
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
var PREVIEW_SERVICE_IMPORT_CODE = "import * as __docsPreviewVue from \"vue\"";
|
|
2026
|
+
var PREVIEW_SERVICE_USE_CODE = [
|
|
2027
|
+
`;const __docsPlayground=new (${PlaygroundService.toString()})(window,__docsPreviewVue);`,
|
|
2028
|
+
"app.provide(\"docs:playground\",__docsPlayground);",
|
|
2029
|
+
"window.$docsPlayground=__docsPlayground;",
|
|
2030
|
+
"app.mixin({beforeUnmount(){if(this===this.$root)__docsPlayground.dispose()}});"
|
|
2031
|
+
].join("");
|
|
2032
|
+
//#endregion
|
|
1736
2033
|
//#region packages/playground/src/core/scss.ts
|
|
1737
2034
|
var SCHEME = "docs-playground";
|
|
1738
2035
|
var STYLE_EXTS = [".scss", ".sass"];
|
|
@@ -1990,8 +2287,8 @@ var createRuntimePreviewOptions = (cdnURL = DEFAULT_CDN_URL, options = {}) => {
|
|
|
1990
2287
|
"</style>"
|
|
1991
2288
|
].join("\n"),
|
|
1992
2289
|
customCode: {
|
|
1993
|
-
importCode: DOCS_LINK_IMPORT_CODE
|
|
1994
|
-
useCode: DOCS_LINK_USE_CODE
|
|
2290
|
+
importCode: `${DOCS_LINK_IMPORT_CODE}\n${PREVIEW_SERVICE_IMPORT_CODE}`,
|
|
2291
|
+
useCode: `${DOCS_LINK_USE_CODE}\n${PREVIEW_SERVICE_USE_CODE}`
|
|
1995
2292
|
}
|
|
1996
2293
|
};
|
|
1997
2294
|
};
|
|
@@ -2072,6 +2369,8 @@ var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
|
|
|
2072
2369
|
const { t } = (0, _deot_docs_locale.useLocale)();
|
|
2073
2370
|
const currentViewport = (0, vue.ref)(props.viewport);
|
|
2074
2371
|
const sandboxRef = (0, vue.ref)(null);
|
|
2372
|
+
const availableHeight = () => Math.max(0, getWindowInnerSize().height - 40 - 44);
|
|
2373
|
+
const temporaryHeight = usePreviewHeightBridge(sandboxRef, availableHeight);
|
|
2075
2374
|
const sandboxKey = (0, vue.ref)(0);
|
|
2076
2375
|
const layoutTick = (0, vue.ref)(0);
|
|
2077
2376
|
const runtimeError = useSandboxRuntimeErrorGuard(sandboxRef);
|
|
@@ -2083,7 +2382,16 @@ var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
|
|
|
2083
2382
|
});
|
|
2084
2383
|
const layout = (0, vue.computed)(() => {
|
|
2085
2384
|
layoutTick.value;
|
|
2086
|
-
|
|
2385
|
+
const base = resolvePopupLayout(currentViewport.value);
|
|
2386
|
+
if (!temporaryHeight.value) return base;
|
|
2387
|
+
const canvasHeight = Math.min(availableHeight(), Math.max(base.canvasHeight, temporaryHeight.value));
|
|
2388
|
+
const shellHeight = Math.max(base.shellHeight, canvasHeight + 44);
|
|
2389
|
+
return {
|
|
2390
|
+
...base,
|
|
2391
|
+
canvasHeight,
|
|
2392
|
+
shellHeight,
|
|
2393
|
+
bodyHeight: shellHeight - 44
|
|
2394
|
+
};
|
|
2087
2395
|
});
|
|
2088
2396
|
const shellStyle = (0, vue.computed)(() => ({
|
|
2089
2397
|
width: `${layout.value.shellWidth}px`,
|
|
@@ -2294,7 +2602,7 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
|
|
|
2294
2602
|
extraHeadHTML
|
|
2295
2603
|
].filter(Boolean).join("\n"),
|
|
2296
2604
|
customCode: {
|
|
2297
|
-
importCode: joinCode(
|
|
2605
|
+
importCode: joinCode(runtimePreviewOptions.customCode?.importCode, props.previewOptions?.customCode?.importCode, props.previewScroller ? PREVIEW_SCROLL_RESET_CODE : void 0, props.previewScroller ? PREVIEW_SCROLLER_IMPORT_CODE : void 0),
|
|
2298
2606
|
useCode: joinCode(runtimePreviewOptions.customCode?.useCode, props.previewOptions?.customCode?.useCode, props.previewScroller ? PREVIEW_SCROLLER_USE_CODE : void 0)
|
|
2299
2607
|
}
|
|
2300
2608
|
};
|
|
@@ -2311,6 +2619,7 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
|
|
|
2311
2619
|
const copyValue = (0, vue.computed)(() => props.files[props.entry] || "");
|
|
2312
2620
|
const store = createRuntimeStore(props.files, props.entry, props.options);
|
|
2313
2621
|
const sandboxRef = (0, vue.ref)(null);
|
|
2622
|
+
const temporaryHeight = usePreviewHeightBridge(sandboxRef);
|
|
2314
2623
|
const sandboxKey = (0, vue.ref)(0);
|
|
2315
2624
|
const runtimeHeight = useSandboxAutoHeight(sandboxRef);
|
|
2316
2625
|
const runtimeError = useSandboxRuntimeErrorGuard(sandboxRef);
|
|
@@ -2364,13 +2673,14 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
|
|
|
2364
2673
|
}
|
|
2365
2674
|
const canExpandPreview = (0, vue.computed)(() => !props.styleless && isPlaygroundExpandable(props.expandable));
|
|
2366
2675
|
const expandLabel = (0, vue.computed)(() => t(previewExpanded.value ? "playground.runtime.collapsePreview" : "playground.runtime.expandPreview"));
|
|
2367
|
-
const
|
|
2676
|
+
const baseViewportHeight = (0, vue.computed)(() => {
|
|
2368
2677
|
if (previewExpanded.value && props.expandable === true) return frozenExpandedHeight.value;
|
|
2369
2678
|
if (previewExpanded.value && isPlaygroundExpandable(props.expandable)) return resolveExpandedPreviewHeight(props.expandable, 0);
|
|
2370
2679
|
const fixedHeight = getViewportHeight(props.viewport);
|
|
2371
2680
|
if (fixedHeight) return fixedHeight;
|
|
2372
2681
|
return runtimeHeight.value;
|
|
2373
2682
|
});
|
|
2683
|
+
const desiredViewportHeight = (0, vue.computed)(() => Math.max(baseViewportHeight.value, temporaryHeight.value));
|
|
2374
2684
|
const scrollExpandedPreviewIntoView = () => {
|
|
2375
2685
|
scrollPlaygroundToViewportStart(runtimeRoot.value);
|
|
2376
2686
|
};
|
|
@@ -2892,6 +3202,7 @@ exports.PREVIEW_SCROLLER_USE_CODE = PREVIEW_SCROLLER_USE_CODE;
|
|
|
2892
3202
|
exports.PREVIEW_SCROLL_CONTENT_CLASS = PREVIEW_SCROLL_CONTENT_CLASS;
|
|
2893
3203
|
exports.PREVIEW_SCROLL_HTML_CLASS = PREVIEW_SCROLL_HTML_CLASS;
|
|
2894
3204
|
exports.Playground = playground_default;
|
|
3205
|
+
exports.PlaygroundService = PlaygroundService;
|
|
2895
3206
|
exports.applyPlaygroundImportMapOverride = applyPlaygroundImportMapOverride;
|
|
2896
3207
|
exports.applyPlaygroundStyleOverride = applyPlaygroundStyleOverride;
|
|
2897
3208
|
exports.clearPlaygroundMaps = clearPlaygroundMaps;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,10 @@ import { Language } from '@deot/docs-locale';
|
|
|
7
7
|
import type { LanguageFn } from 'highlight.js';
|
|
8
8
|
import { Portal } from '@deot/vc';
|
|
9
9
|
import { PublicProps } from 'vue';
|
|
10
|
+
import type { Ref } from 'vue';
|
|
10
11
|
import type { SandboxProps } from '@vue/repl';
|
|
11
12
|
import type { StoreState } from '@vue/repl';
|
|
13
|
+
import { vue } from 'vue';
|
|
12
14
|
|
|
13
15
|
declare const __VLS_export: DefineComponent<__VLS_Props, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
14
16
|
language: string;
|
|
@@ -187,6 +189,34 @@ export declare type PlaygroundPreviewOptions = SandboxProps['previewOptions'];
|
|
|
187
189
|
|
|
188
190
|
export declare type PlaygroundPreviewScroller = boolean;
|
|
189
191
|
|
|
192
|
+
export declare interface PlaygroundRunOptions {
|
|
193
|
+
visible?: Ref<boolean> | (() => boolean);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export declare class PlaygroundService {
|
|
197
|
+
private readonly target;
|
|
198
|
+
private readonly vue;
|
|
199
|
+
private readonly session;
|
|
200
|
+
private sequence;
|
|
201
|
+
private disposed;
|
|
202
|
+
private manual?;
|
|
203
|
+
private readonly requests;
|
|
204
|
+
private readonly owners;
|
|
205
|
+
constructor(target: Window, vue: PreviewVue);
|
|
206
|
+
enable(height: number): Promise<boolean>;
|
|
207
|
+
disable(): void;
|
|
208
|
+
run(height: number): (visible: boolean) => Promise<boolean | undefined>;
|
|
209
|
+
run<Args extends unknown[], Result>(height: number, handler: (...args: Args) => Result): (...args: Args) => Promise<Awaited<Result> | undefined>;
|
|
210
|
+
run<Args extends unknown[], Result>(height: number, options: PlaygroundRunOptions, handler: (...args: Args) => Result): (...args: Args) => Promise<Awaited<Result> | undefined>;
|
|
211
|
+
run(height: number, options: PlaygroundRunOptions & {
|
|
212
|
+
visible: Ref<boolean>;
|
|
213
|
+
}): () => Promise<void | undefined>;
|
|
214
|
+
dispose(): void;
|
|
215
|
+
private validateHeight;
|
|
216
|
+
private post;
|
|
217
|
+
private createRequest;
|
|
218
|
+
}
|
|
219
|
+
|
|
190
220
|
export declare type PlaygroundView = 'runtime' | 'files';
|
|
191
221
|
|
|
192
222
|
export declare type PlaygroundViewport = 'auto' | number | [width: number, height: number];
|
|
@@ -196,12 +226,18 @@ export declare interface PlaygroundViewsProps {
|
|
|
196
226
|
activeView: PlaygroundView;
|
|
197
227
|
}
|
|
198
228
|
|
|
229
|
+
export declare interface PlaygroundWindow extends Window {
|
|
230
|
+
$docsPlayground: PlaygroundService;
|
|
231
|
+
}
|
|
232
|
+
|
|
199
233
|
export declare const PREVIEW_SCROLL_CONTENT_CLASS = "docs-playground-preview-scroll__content";
|
|
200
234
|
|
|
201
235
|
export declare const PREVIEW_SCROLL_HTML_CLASS = "docs-playground-preview-scroll";
|
|
202
236
|
|
|
203
237
|
export declare const PREVIEW_SCROLLER_USE_CODE: string;
|
|
204
238
|
|
|
239
|
+
declare type PreviewVue = Pick<vue, 'getCurrentScope' | 'onScopeDispose' | 'watch' | 'toValue' | 'isRef'>;
|
|
240
|
+
|
|
205
241
|
export declare const registerVueHighlight: (api?: HLJSApi) => HLJSApi;
|
|
206
242
|
|
|
207
243
|
export declare const removePlaygroundImportMapOverride: (alias: string) => {
|
package/dist/index.js
CHANGED
|
@@ -869,6 +869,8 @@ var PREVIEW_SCROLLER_USE_CODE = [
|
|
|
869
869
|
"if(app._context&&next._context){",
|
|
870
870
|
"if(app._context.components)Object.assign(next._context.components,app._context.components);",
|
|
871
871
|
"if(app._context.directives)Object.assign(next._context.directives,app._context.directives);",
|
|
872
|
+
"Object.assign(next._context.provides,app._context.provides);",
|
|
873
|
+
"next._context.mixins.push(...app._context.mixins);",
|
|
872
874
|
"}",
|
|
873
875
|
"window.__app__=next;",
|
|
874
876
|
`document.documentElement.classList.add("${PREVIEW_SCROLL_HTML_CLASS}");`,
|
|
@@ -915,6 +917,7 @@ var useSandboxAutoHeight = (sandboxRef) => {
|
|
|
915
917
|
const collapsedTop = Math.max(0, bodyRect.top - rootTop);
|
|
916
918
|
let childrenBottom = 0;
|
|
917
919
|
for (const child of body.children) {
|
|
920
|
+
if (iframeWindow.getComputedStyle(child).position === "fixed") continue;
|
|
918
921
|
const rect = child.getBoundingClientRect();
|
|
919
922
|
const marginBottom = Number.parseFloat(iframeWindow.getComputedStyle(child).marginBottom) || 0;
|
|
920
923
|
childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
|
|
@@ -935,9 +938,14 @@ var useSandboxAutoHeight = (sandboxRef) => {
|
|
|
935
938
|
}
|
|
936
939
|
return Math.ceil(Math.max(scrollContent.scrollHeight + margins, scrollContent.offsetHeight + margins, childrenBottom + margins));
|
|
937
940
|
};
|
|
938
|
-
const
|
|
941
|
+
const isPendingPreviewApp = (iframeDocument) => {
|
|
939
942
|
const app = iframeDocument.getElementById("app");
|
|
940
|
-
return
|
|
943
|
+
return app ? app.childElementCount === 0 : !!iframe?.srcdoc;
|
|
944
|
+
};
|
|
945
|
+
const releaseBootstrapHeight = (target) => {
|
|
946
|
+
nextTick(() => {
|
|
947
|
+
if (iframe === target) target.style.height = "100%";
|
|
948
|
+
});
|
|
941
949
|
};
|
|
942
950
|
const measure = () => {
|
|
943
951
|
frameId = 0;
|
|
@@ -947,10 +955,11 @@ var useSandboxAutoHeight = (sandboxRef) => {
|
|
|
947
955
|
const iframeDocument = iframe.contentDocument;
|
|
948
956
|
if (!iframeWindow || !iframeDocument?.body) return;
|
|
949
957
|
const scrollContent = resolveScrollContent(iframeDocument);
|
|
950
|
-
if (!scrollContent &&
|
|
958
|
+
if (!scrollContent && isPendingPreviewApp(iframeDocument)) return;
|
|
951
959
|
const contentHeight = scrollContent ? measureScrollContent(scrollContent) : measureDocumentContent(iframeWindow, iframeDocument);
|
|
952
960
|
const nextHeight = Math.max(contentHeight, 24);
|
|
953
961
|
if (height.value !== nextHeight) height.value = nextHeight;
|
|
962
|
+
releaseBootstrapHeight(iframe);
|
|
954
963
|
} catch {
|
|
955
964
|
if (height.value !== 24) height.value = 24;
|
|
956
965
|
}
|
|
@@ -1019,7 +1028,7 @@ var useSandboxAutoHeight = (sandboxRef) => {
|
|
|
1019
1028
|
return;
|
|
1020
1029
|
}
|
|
1021
1030
|
iframe.addEventListener("load", handleIframeLoad);
|
|
1022
|
-
iframe.style.height =
|
|
1031
|
+
iframe.style.height = `${Math.max(window.innerHeight, 24)}px`;
|
|
1023
1032
|
observeContent();
|
|
1024
1033
|
};
|
|
1025
1034
|
const syncIframe = () => setIframe(container?.querySelector("iframe") || null);
|
|
@@ -1369,6 +1378,73 @@ var useSandboxTheme = (sandboxRef) => {
|
|
|
1369
1378
|
});
|
|
1370
1379
|
};
|
|
1371
1380
|
//#endregion
|
|
1381
|
+
//#region packages/playground/src/core/runtime/height-bridge.ts
|
|
1382
|
+
/**
|
|
1383
|
+
* 只接收本宿主当前 iframe 的高度请求,应用重评时切换会话。
|
|
1384
|
+
* @param sandbox 预览容器。
|
|
1385
|
+
* @param maxHeight 当前可用高度,普通预览默认不限制。
|
|
1386
|
+
* @returns 所有有效请求的最大高度。
|
|
1387
|
+
*/
|
|
1388
|
+
function usePreviewHeightBridge(sandbox, maxHeight = () => Infinity) {
|
|
1389
|
+
const height = ref(0);
|
|
1390
|
+
const requests = /* @__PURE__ */ new Map();
|
|
1391
|
+
let source = null;
|
|
1392
|
+
let session = "";
|
|
1393
|
+
let disposed = false;
|
|
1394
|
+
const sync = () => {
|
|
1395
|
+
height.value = Math.max(0, ...requests.values());
|
|
1396
|
+
};
|
|
1397
|
+
const reset = () => {
|
|
1398
|
+
requests.clear();
|
|
1399
|
+
session = "";
|
|
1400
|
+
source = null;
|
|
1401
|
+
sync();
|
|
1402
|
+
};
|
|
1403
|
+
const handleMessage = async (event) => {
|
|
1404
|
+
const iframe = resolveSandboxContainer(sandbox.value)?.querySelector("iframe");
|
|
1405
|
+
const data = event.data;
|
|
1406
|
+
if (!iframe?.contentWindow || event.source !== iframe.contentWindow || data?.action !== "docs:height" || typeof data.session !== "string") return;
|
|
1407
|
+
if (data.operation === "start") {
|
|
1408
|
+
reset();
|
|
1409
|
+
source = iframe.contentWindow;
|
|
1410
|
+
session = data.session;
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (source !== iframe.contentWindow || session !== data.session) return;
|
|
1414
|
+
if (data.operation === "stop") {
|
|
1415
|
+
reset();
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
if (!Number.isSafeInteger(data.id) || data.id <= 0) return;
|
|
1419
|
+
if (data.operation === "release") {
|
|
1420
|
+
requests.delete(data.id);
|
|
1421
|
+
sync();
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
if (data.operation !== "enable" || !Number.isFinite(data.height) || data.height <= 0) return;
|
|
1425
|
+
const accepted = data.height <= maxHeight();
|
|
1426
|
+
if (accepted) requests.set(data.id, data.height);
|
|
1427
|
+
sync();
|
|
1428
|
+
const receiver = source;
|
|
1429
|
+
await nextTick();
|
|
1430
|
+
if (disposed || source !== receiver || session !== data.session) return;
|
|
1431
|
+
receiver?.postMessage({
|
|
1432
|
+
action: "docs:height:applied",
|
|
1433
|
+
session,
|
|
1434
|
+
id: data.id,
|
|
1435
|
+
accepted
|
|
1436
|
+
}, "*");
|
|
1437
|
+
};
|
|
1438
|
+
watch(() => resolveSandboxContainer(sandbox.value), reset, { flush: "sync" });
|
|
1439
|
+
if (typeof window !== "undefined") window.addEventListener("message", handleMessage);
|
|
1440
|
+
onBeforeUnmount(() => {
|
|
1441
|
+
disposed = true;
|
|
1442
|
+
if (typeof window !== "undefined") window.removeEventListener("message", handleMessage);
|
|
1443
|
+
reset();
|
|
1444
|
+
});
|
|
1445
|
+
return height;
|
|
1446
|
+
}
|
|
1447
|
+
//#endregion
|
|
1372
1448
|
//#region packages/playground/src/core/runtime/toolbar.vue?vue&type=script&setup=true&lang.ts
|
|
1373
1449
|
var _hoisted_1$3 = { class: "docs-playground__tools" };
|
|
1374
1450
|
var _hoisted_2$3 = {
|
|
@@ -1613,7 +1689,9 @@ var BUILTIN_IMPORT_ASSETS = {
|
|
|
1613
1689
|
"@deot/helper": "@deot/helper/dist/index.js",
|
|
1614
1690
|
"normalize-wheel": "normalize-wheel-es/dist/index.mjs",
|
|
1615
1691
|
"photoswipe": "photoswipe/dist/photoswipe.esm.js",
|
|
1616
|
-
"photoswipe/lightbox": "photoswipe/dist/photoswipe-lightbox.esm.js"
|
|
1692
|
+
"photoswipe/lightbox": "photoswipe/dist/photoswipe-lightbox.esm.js",
|
|
1693
|
+
"vue": "vue/dist/vue.runtime.esm-browser.js",
|
|
1694
|
+
"vue/server-renderer": "vue/dist/server-renderer.esm-browser.js"
|
|
1617
1695
|
};
|
|
1618
1696
|
var normalizeCdnURL = (cdnURL = DEFAULT_CDN_URL) => {
|
|
1619
1697
|
return cdnURL.trim().replace(/\/+$/u, "") || "https://cdn.jsdelivr.net/npm";
|
|
@@ -1624,9 +1702,7 @@ var SASS_CDN_URL = jsdelivrEsmURL("sass/+esm");
|
|
|
1624
1702
|
var cdnAsset = (cdnURL, asset) => cdnJoin(normalizeCdnURL(cdnURL), asset);
|
|
1625
1703
|
var createBuiltinImports = (cdnURL = DEFAULT_CDN_URL) => ({
|
|
1626
1704
|
...Object.fromEntries(Object.entries(BUILTIN_IMPORT_ASSETS).map(([specifier, asset]) => [specifier, cdnAsset(cdnURL, asset)])),
|
|
1627
|
-
"lodash-es": jsdelivrEsmURL("lodash-es/+esm")
|
|
1628
|
-
"vue": "https://play.vuejs.org/vue.runtime.esm-browser.js",
|
|
1629
|
-
"vue/server-renderer": "https://play.vuejs.org/server-renderer.esm-browser.js"
|
|
1705
|
+
"lodash-es": jsdelivrEsmURL("lodash-es/+esm")
|
|
1630
1706
|
});
|
|
1631
1707
|
var createBuiltinStyles = (cdnURL = DEFAULT_CDN_URL) => Object.fromEntries(PREVIEW_STYLE_ASSETS.map((asset) => [asset, cdnAsset(cdnURL, asset)]));
|
|
1632
1708
|
//#endregion
|
|
@@ -1706,6 +1782,227 @@ var clearPlaygroundMaps = () => {
|
|
|
1706
1782
|
].forEach((store) => store.set());
|
|
1707
1783
|
};
|
|
1708
1784
|
//#endregion
|
|
1785
|
+
//#region packages/playground/src/preview-service.ts
|
|
1786
|
+
/** Preview iframe 内的高度服务;class 会被序列化到 iframe 执行。 */
|
|
1787
|
+
var PlaygroundService = class {
|
|
1788
|
+
target;
|
|
1789
|
+
vue;
|
|
1790
|
+
session;
|
|
1791
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1792
|
+
sequence = 0;
|
|
1793
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1794
|
+
disposed = false;
|
|
1795
|
+
manual;
|
|
1796
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1797
|
+
requests = /* @__PURE__ */ new Set();
|
|
1798
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1799
|
+
owners = /* @__PURE__ */ new Set();
|
|
1800
|
+
/**
|
|
1801
|
+
* @param target 当前预览窗口。
|
|
1802
|
+
* @param vue 与预览应用共用的 Vue 实例。
|
|
1803
|
+
*/
|
|
1804
|
+
/* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
|
|
1805
|
+
constructor(target, vue) {
|
|
1806
|
+
this.target = target;
|
|
1807
|
+
this.vue = vue;
|
|
1808
|
+
this.session = `${Date.now()}-${Math.random()}`;
|
|
1809
|
+
this.dispose = this.dispose.bind(this);
|
|
1810
|
+
this.post("start");
|
|
1811
|
+
this.target.addEventListener("pagehide", this.dispose);
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* 申请手动临时高度;新请求会替换上一个手动请求。
|
|
1815
|
+
* @param height iframe 最小高度。
|
|
1816
|
+
* @returns iframe 是否在超时前应用目标高度。
|
|
1817
|
+
*/
|
|
1818
|
+
enable(height) {
|
|
1819
|
+
this.validateHeight(height);
|
|
1820
|
+
this.manual?.release();
|
|
1821
|
+
this.manual = this.createRequest(height);
|
|
1822
|
+
return this.manual.ready;
|
|
1823
|
+
}
|
|
1824
|
+
/** 释放手动临时高度,不影响 `run` 创建的请求。 */
|
|
1825
|
+
disable() {
|
|
1826
|
+
this.manual?.release();
|
|
1827
|
+
this.manual = void 0;
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* 创建等待高度后执行的事件处理函数。
|
|
1831
|
+
* @param height iframe 最小高度。
|
|
1832
|
+
* @param optionsOrHandler 可见状态配置,或最后一个 handler。
|
|
1833
|
+
* @param lastHandler 使用配置时的最后一个 handler。
|
|
1834
|
+
* @returns 保留 handler 参数和结果的异步事件处理函数。
|
|
1835
|
+
*/
|
|
1836
|
+
run(height, optionsOrHandler, lastHandler) {
|
|
1837
|
+
this.validateHeight(height);
|
|
1838
|
+
if (optionsOrHandler === void 0) return (visible) => {
|
|
1839
|
+
if (visible) return this.enable(height);
|
|
1840
|
+
this.disable();
|
|
1841
|
+
return Promise.resolve(void 0);
|
|
1842
|
+
};
|
|
1843
|
+
const visible = (typeof optionsOrHandler === "function" ? {} : optionsOrHandler).visible;
|
|
1844
|
+
if (visible !== void 0 && !this.vue.isRef(visible) && typeof visible !== "function") throw new TypeError("Playground visible must be a ref or getter");
|
|
1845
|
+
const handler = typeof optionsOrHandler === "function" ? optionsOrHandler : lastHandler ?? (this.vue.isRef(visible) ? () => {
|
|
1846
|
+
visible.value = true;
|
|
1847
|
+
} : void 0);
|
|
1848
|
+
if (typeof handler !== "function") throw new TypeError("Playground run requires a handler");
|
|
1849
|
+
let ownerDisposed = false;
|
|
1850
|
+
let current;
|
|
1851
|
+
let pending;
|
|
1852
|
+
const disposeOwner = () => {
|
|
1853
|
+
ownerDisposed = true;
|
|
1854
|
+
current?.release();
|
|
1855
|
+
this.owners.delete(disposeOwner);
|
|
1856
|
+
};
|
|
1857
|
+
this.owners.add(disposeOwner);
|
|
1858
|
+
if (this.vue.getCurrentScope()) this.vue.onScopeDispose(disposeOwner);
|
|
1859
|
+
return (...args) => {
|
|
1860
|
+
if (this.disposed || ownerDisposed) return Promise.resolve(void 0);
|
|
1861
|
+
if (pending) return pending;
|
|
1862
|
+
const request = current = this.createRequest(height);
|
|
1863
|
+
let executionFinished = false;
|
|
1864
|
+
const unlock = () => {
|
|
1865
|
+
if (executionFinished && request.released && current === request) {
|
|
1866
|
+
pending = void 0;
|
|
1867
|
+
current = void 0;
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
request.closed.then(unlock);
|
|
1871
|
+
const execution = (async () => {
|
|
1872
|
+
try {
|
|
1873
|
+
if (!await request.ready || ownerDisposed || this.disposed) return void 0;
|
|
1874
|
+
if (visible) request.watch(visible);
|
|
1875
|
+
const result = await handler(...args);
|
|
1876
|
+
if (!visible || !this.vue.toValue(visible)) request.release();
|
|
1877
|
+
return result;
|
|
1878
|
+
} catch (error) {
|
|
1879
|
+
request.release();
|
|
1880
|
+
throw error;
|
|
1881
|
+
} finally {
|
|
1882
|
+
executionFinished = true;
|
|
1883
|
+
unlock();
|
|
1884
|
+
}
|
|
1885
|
+
})();
|
|
1886
|
+
pending = execution;
|
|
1887
|
+
return execution;
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
/** 释放当前预览应用持有的全部高度和监听。 */
|
|
1891
|
+
/* istanbul ignore next -- 注入测试会从序列化后的 iframe class 调用该方法。 */
|
|
1892
|
+
dispose() {
|
|
1893
|
+
if (this.disposed) return;
|
|
1894
|
+
this.disposed = true;
|
|
1895
|
+
for (const disposeOwner of this.owners) disposeOwner();
|
|
1896
|
+
for (const request of this.requests) request.release();
|
|
1897
|
+
this.post("stop");
|
|
1898
|
+
this.target.removeEventListener("pagehide", this.dispose);
|
|
1899
|
+
if (this.target.$docsPlayground === this) delete this.target.$docsPlayground;
|
|
1900
|
+
}
|
|
1901
|
+
/** @param height iframe 最小高度。 */
|
|
1902
|
+
validateHeight(height) {
|
|
1903
|
+
if (!Number.isFinite(height) || height <= 0) throw new TypeError("Playground height must be a positive finite number");
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* @param operation 请求操作。
|
|
1907
|
+
* @param id 请求标识。
|
|
1908
|
+
* @param height iframe 最小高度。
|
|
1909
|
+
*/
|
|
1910
|
+
/* istanbul ignore next -- 构造和销毁时会从序列化后的 iframe class 调用该方法。 */
|
|
1911
|
+
post(operation, id = 0, height = 0) {
|
|
1912
|
+
this.target.parent.postMessage({
|
|
1913
|
+
action: "docs:height",
|
|
1914
|
+
session: this.session,
|
|
1915
|
+
operation,
|
|
1916
|
+
id,
|
|
1917
|
+
height
|
|
1918
|
+
}, "*");
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* @param height iframe 最小高度。
|
|
1922
|
+
* @returns 内部等待与释放句柄。
|
|
1923
|
+
*/
|
|
1924
|
+
createRequest(height) {
|
|
1925
|
+
this.validateHeight(height);
|
|
1926
|
+
const id = ++this.sequence;
|
|
1927
|
+
let released = false;
|
|
1928
|
+
let settled = false;
|
|
1929
|
+
let frame = 0;
|
|
1930
|
+
let timer = 0;
|
|
1931
|
+
let stopWatch;
|
|
1932
|
+
let resolveReady;
|
|
1933
|
+
let resolveClosed;
|
|
1934
|
+
const ready = new Promise((resolve) => {
|
|
1935
|
+
resolveReady = resolve;
|
|
1936
|
+
});
|
|
1937
|
+
const closed = new Promise((resolve) => {
|
|
1938
|
+
resolveClosed = resolve;
|
|
1939
|
+
});
|
|
1940
|
+
const stopWaiting = () => {
|
|
1941
|
+
this.target.cancelAnimationFrame(frame);
|
|
1942
|
+
if (timer) {
|
|
1943
|
+
this.target.clearTimeout(timer);
|
|
1944
|
+
timer = 0;
|
|
1945
|
+
}
|
|
1946
|
+
this.target.removeEventListener("message", onMessage);
|
|
1947
|
+
};
|
|
1948
|
+
const release = () => {
|
|
1949
|
+
if (released) return;
|
|
1950
|
+
released = true;
|
|
1951
|
+
stopWaiting();
|
|
1952
|
+
stopWatch?.();
|
|
1953
|
+
resolveReady(false);
|
|
1954
|
+
resolveClosed();
|
|
1955
|
+
this.requests.delete(request);
|
|
1956
|
+
this.post("release", id);
|
|
1957
|
+
};
|
|
1958
|
+
const check = () => {
|
|
1959
|
+
if (released) return;
|
|
1960
|
+
if (this.target.innerHeight >= height) {
|
|
1961
|
+
settled = true;
|
|
1962
|
+
stopWaiting();
|
|
1963
|
+
resolveReady(true);
|
|
1964
|
+
} else frame = this.target.requestAnimationFrame(check);
|
|
1965
|
+
};
|
|
1966
|
+
const onMessage = (event) => {
|
|
1967
|
+
const data = event.data;
|
|
1968
|
+
if (event.source !== this.target.parent || data?.action !== "docs:height:applied" || data.session !== this.session || data.id !== id || settled) return;
|
|
1969
|
+
if (!data.accepted) release();
|
|
1970
|
+
else check();
|
|
1971
|
+
};
|
|
1972
|
+
const request = {
|
|
1973
|
+
ready,
|
|
1974
|
+
closed,
|
|
1975
|
+
get released() {
|
|
1976
|
+
return released;
|
|
1977
|
+
},
|
|
1978
|
+
release,
|
|
1979
|
+
watch: (visible) => {
|
|
1980
|
+
stopWatch = this.vue.watch(() => this.vue.toValue(visible), (value) => {
|
|
1981
|
+
if (!value) release();
|
|
1982
|
+
}, { flush: "sync" });
|
|
1983
|
+
}
|
|
1984
|
+
};
|
|
1985
|
+
this.requests.add(request);
|
|
1986
|
+
if (this.disposed) release();
|
|
1987
|
+
else {
|
|
1988
|
+
this.target.addEventListener("message", onMessage);
|
|
1989
|
+
timer = this.target.setTimeout(() => {
|
|
1990
|
+
timer = 0;
|
|
1991
|
+
release();
|
|
1992
|
+
}, 2e3);
|
|
1993
|
+
this.post("enable", id, height);
|
|
1994
|
+
}
|
|
1995
|
+
return request;
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
var PREVIEW_SERVICE_IMPORT_CODE = "import * as __docsPreviewVue from \"vue\"";
|
|
1999
|
+
var PREVIEW_SERVICE_USE_CODE = [
|
|
2000
|
+
`;const __docsPlayground=new (${PlaygroundService.toString()})(window,__docsPreviewVue);`,
|
|
2001
|
+
"app.provide(\"docs:playground\",__docsPlayground);",
|
|
2002
|
+
"window.$docsPlayground=__docsPlayground;",
|
|
2003
|
+
"app.mixin({beforeUnmount(){if(this===this.$root)__docsPlayground.dispose()}});"
|
|
2004
|
+
].join("");
|
|
2005
|
+
//#endregion
|
|
1709
2006
|
//#region packages/playground/src/core/scss.ts
|
|
1710
2007
|
var SCHEME = "docs-playground";
|
|
1711
2008
|
var STYLE_EXTS = [".scss", ".sass"];
|
|
@@ -1963,8 +2260,8 @@ var createRuntimePreviewOptions = (cdnURL = DEFAULT_CDN_URL, options = {}) => {
|
|
|
1963
2260
|
"</style>"
|
|
1964
2261
|
].join("\n"),
|
|
1965
2262
|
customCode: {
|
|
1966
|
-
importCode: DOCS_LINK_IMPORT_CODE
|
|
1967
|
-
useCode: DOCS_LINK_USE_CODE
|
|
2263
|
+
importCode: `${DOCS_LINK_IMPORT_CODE}\n${PREVIEW_SERVICE_IMPORT_CODE}`,
|
|
2264
|
+
useCode: `${DOCS_LINK_USE_CODE}\n${PREVIEW_SERVICE_USE_CODE}`
|
|
1968
2265
|
}
|
|
1969
2266
|
};
|
|
1970
2267
|
};
|
|
@@ -2045,6 +2342,8 @@ var Alone = new Portal(/* @__PURE__ */ defineComponent({
|
|
|
2045
2342
|
const { t } = useLocale();
|
|
2046
2343
|
const currentViewport = ref(props.viewport);
|
|
2047
2344
|
const sandboxRef = ref(null);
|
|
2345
|
+
const availableHeight = () => Math.max(0, getWindowInnerSize().height - 40 - 44);
|
|
2346
|
+
const temporaryHeight = usePreviewHeightBridge(sandboxRef, availableHeight);
|
|
2048
2347
|
const sandboxKey = ref(0);
|
|
2049
2348
|
const layoutTick = ref(0);
|
|
2050
2349
|
const runtimeError = useSandboxRuntimeErrorGuard(sandboxRef);
|
|
@@ -2056,7 +2355,16 @@ var Alone = new Portal(/* @__PURE__ */ defineComponent({
|
|
|
2056
2355
|
});
|
|
2057
2356
|
const layout = computed(() => {
|
|
2058
2357
|
layoutTick.value;
|
|
2059
|
-
|
|
2358
|
+
const base = resolvePopupLayout(currentViewport.value);
|
|
2359
|
+
if (!temporaryHeight.value) return base;
|
|
2360
|
+
const canvasHeight = Math.min(availableHeight(), Math.max(base.canvasHeight, temporaryHeight.value));
|
|
2361
|
+
const shellHeight = Math.max(base.shellHeight, canvasHeight + 44);
|
|
2362
|
+
return {
|
|
2363
|
+
...base,
|
|
2364
|
+
canvasHeight,
|
|
2365
|
+
shellHeight,
|
|
2366
|
+
bodyHeight: shellHeight - 44
|
|
2367
|
+
};
|
|
2060
2368
|
});
|
|
2061
2369
|
const shellStyle = computed(() => ({
|
|
2062
2370
|
width: `${layout.value.shellWidth}px`,
|
|
@@ -2264,7 +2572,7 @@ var runtime_default = /* @__PURE__ */ defineComponent({
|
|
|
2264
2572
|
extraHeadHTML
|
|
2265
2573
|
].filter(Boolean).join("\n"),
|
|
2266
2574
|
customCode: {
|
|
2267
|
-
importCode: joinCode(
|
|
2575
|
+
importCode: joinCode(runtimePreviewOptions.customCode?.importCode, props.previewOptions?.customCode?.importCode, props.previewScroller ? PREVIEW_SCROLL_RESET_CODE : void 0, props.previewScroller ? PREVIEW_SCROLLER_IMPORT_CODE : void 0),
|
|
2268
2576
|
useCode: joinCode(runtimePreviewOptions.customCode?.useCode, props.previewOptions?.customCode?.useCode, props.previewScroller ? PREVIEW_SCROLLER_USE_CODE : void 0)
|
|
2269
2577
|
}
|
|
2270
2578
|
};
|
|
@@ -2281,6 +2589,7 @@ var runtime_default = /* @__PURE__ */ defineComponent({
|
|
|
2281
2589
|
const copyValue = computed(() => props.files[props.entry] || "");
|
|
2282
2590
|
const store = createRuntimeStore(props.files, props.entry, props.options);
|
|
2283
2591
|
const sandboxRef = ref(null);
|
|
2592
|
+
const temporaryHeight = usePreviewHeightBridge(sandboxRef);
|
|
2284
2593
|
const sandboxKey = ref(0);
|
|
2285
2594
|
const runtimeHeight = useSandboxAutoHeight(sandboxRef);
|
|
2286
2595
|
const runtimeError = useSandboxRuntimeErrorGuard(sandboxRef);
|
|
@@ -2334,13 +2643,14 @@ var runtime_default = /* @__PURE__ */ defineComponent({
|
|
|
2334
2643
|
}
|
|
2335
2644
|
const canExpandPreview = computed(() => !props.styleless && isPlaygroundExpandable(props.expandable));
|
|
2336
2645
|
const expandLabel = computed(() => t(previewExpanded.value ? "playground.runtime.collapsePreview" : "playground.runtime.expandPreview"));
|
|
2337
|
-
const
|
|
2646
|
+
const baseViewportHeight = computed(() => {
|
|
2338
2647
|
if (previewExpanded.value && props.expandable === true) return frozenExpandedHeight.value;
|
|
2339
2648
|
if (previewExpanded.value && isPlaygroundExpandable(props.expandable)) return resolveExpandedPreviewHeight(props.expandable, 0);
|
|
2340
2649
|
const fixedHeight = getViewportHeight(props.viewport);
|
|
2341
2650
|
if (fixedHeight) return fixedHeight;
|
|
2342
2651
|
return runtimeHeight.value;
|
|
2343
2652
|
});
|
|
2653
|
+
const desiredViewportHeight = computed(() => Math.max(baseViewportHeight.value, temporaryHeight.value));
|
|
2344
2654
|
const scrollExpandedPreviewIntoView = () => {
|
|
2345
2655
|
scrollPlaygroundToViewportStart(runtimeRoot.value);
|
|
2346
2656
|
};
|
|
@@ -2855,4 +3165,4 @@ var playground_default = /* @__PURE__ */ defineComponent({
|
|
|
2855
3165
|
}
|
|
2856
3166
|
});
|
|
2857
3167
|
//#endregion
|
|
2858
|
-
export { code_preview_default as CodePreview, DEFAULT_CDN_URL, Editor, PREVIEW_SCROLLER_USE_CODE, PREVIEW_SCROLL_CONTENT_CLASS, PREVIEW_SCROLL_HTML_CLASS, playground_default as Playground, applyPlaygroundImportMapOverride, applyPlaygroundStyleOverride, clearPlaygroundMaps, createBuiltinImports, createBuiltinStyles, createRuntimePreviewOptions, getPlaygroundImportMapOverrides, getPlaygroundSiteModules, getPlaygroundSiteStyles, getPlaygroundStyleOverrides, highlightCode, highlightCodeByLanguage, isSafePlaygroundHref, normalizeCdnURL, registerVueHighlight, removePlaygroundImportMapOverride, removePlaygroundStyleOverride, resolveHighlightLanguage, resolvePlaygroundTitleId, setPlaygroundImportMapOverrides, setPlaygroundSiteModules, setPlaygroundSiteStyles, setPlaygroundStyleOverrides, slugifyPlaygroundTitle, vueHighlight };
|
|
3168
|
+
export { code_preview_default as CodePreview, DEFAULT_CDN_URL, Editor, PREVIEW_SCROLLER_USE_CODE, PREVIEW_SCROLL_CONTENT_CLASS, PREVIEW_SCROLL_HTML_CLASS, playground_default as Playground, PlaygroundService, applyPlaygroundImportMapOverride, applyPlaygroundStyleOverride, clearPlaygroundMaps, createBuiltinImports, createBuiltinStyles, createRuntimePreviewOptions, getPlaygroundImportMapOverrides, getPlaygroundSiteModules, getPlaygroundSiteStyles, getPlaygroundStyleOverrides, highlightCode, highlightCodeByLanguage, isSafePlaygroundHref, normalizeCdnURL, registerVueHighlight, removePlaygroundImportMapOverride, removePlaygroundStyleOverride, resolveHighlightLanguage, resolvePlaygroundTitleId, setPlaygroundImportMapOverrides, setPlaygroundSiteModules, setPlaygroundSiteStyles, setPlaygroundStyleOverrides, slugifyPlaygroundTitle, vueHighlight };
|