@deot/docs-playground 1.1.9 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs DELETED
@@ -1,4320 +0,0 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_css = require("./css-DGNVJsLz.cjs");
3
- let vue = require("vue");
4
- vue = require_css.__toESM(vue);
5
- let _deot_vc = require("@deot/vc");
6
- _deot_vc = require_css.__toESM(_deot_vc);
7
- let _deot_docs_locale = require("@deot/docs-locale");
8
- let highlight_js = require("highlight.js");
9
- highlight_js = require_css.__toESM(highlight_js);
10
- let _vue_repl = require("@vue/repl");
11
- let codemirror = require("codemirror");
12
- let _codemirror_lang_css = require("@codemirror/lang-css");
13
- let _codemirror_lang_javascript = require("@codemirror/lang-javascript");
14
- let _codemirror_lang_vue = require("@codemirror/lang-vue");
15
- let _codemirror_view = require("@codemirror/view");
16
- let es_module_lexer = require("es-module-lexer");
17
- let sucrase = require("sucrase");
18
- let _vue_compiler_sfc_dist_compiler_sfc_esm_browser_js = require("@vue/compiler-sfc/dist/compiler-sfc.esm-browser.js");
19
- _vue_compiler_sfc_dist_compiler_sfc_esm_browser_js = require_css.__toESM(_vue_compiler_sfc_dist_compiler_sfc_esm_browser_js);
20
- let vue_router = require("vue-router");
21
- vue_router = require_css.__toESM(vue_router);
22
- let pinia = require("pinia");
23
- pinia = require_css.__toESM(pinia);
24
- let _deot_vc_components = require("@deot/vc-components");
25
- _deot_vc_components = require_css.__toESM(_deot_vc_components);
26
- let _deot_vc_hooks = require("@deot/vc-hooks");
27
- _deot_vc_hooks = require_css.__toESM(_deot_vc_hooks);
28
- //#region packages/playground/src/highlight.ts
29
- var vueHighlight = (api) => ({
30
- subLanguage: "xml",
31
- contains: [
32
- api.COMMENT("<!--", "-->", { relevance: 10 }),
33
- {
34
- begin: /^(\s*)(<script>)/gm,
35
- end: /^(\s*)(<\/script>)/gm,
36
- subLanguage: "javascript",
37
- excludeBegin: true,
38
- excludeEnd: true
39
- },
40
- {
41
- begin: /^(?:\s*)(?:<script\s+lang=(["'])ts\1>)/gm,
42
- end: /^(\s*)(<\/script>)/gm,
43
- subLanguage: "typescript",
44
- excludeBegin: true,
45
- excludeEnd: true
46
- },
47
- {
48
- begin: /^(\s*)(<style(\s+scoped)?>)/gm,
49
- end: /^(\s*)(<\/style>)/gm,
50
- subLanguage: "css",
51
- excludeBegin: true,
52
- excludeEnd: true
53
- },
54
- {
55
- begin: /^(?:\s*)(?:<style(?:\s+scoped)?\s+lang=(["'])(?:s[ca]ss)\1(?:\s+scoped)?>)/gm,
56
- end: /^(\s*)(<\/style>)/gm,
57
- subLanguage: "scss",
58
- excludeBegin: true,
59
- excludeEnd: true
60
- },
61
- {
62
- begin: /^(?:\s*)(?:<style(?:\s+scoped)?\s+lang=(["'])stylus\1(?:\s+scoped)?>)/gm,
63
- end: /^(\s*)(<\/style>)/gm,
64
- subLanguage: "stylus",
65
- excludeBegin: true,
66
- excludeEnd: true
67
- }
68
- ]
69
- });
70
- var registerVueHighlight = (api = highlight_js.default) => {
71
- if (!api.getLanguage("vue")) api.registerLanguage("vue", vueHighlight);
72
- return api;
73
- };
74
- var resolveHighlightLanguage = (filename) => {
75
- if (/\.vue$/i.test(filename)) return "vue";
76
- if (/\.tsx?$/i.test(filename)) return "typescript";
77
- if (/\.jsx?$/i.test(filename)) return "javascript";
78
- if (/\.html$/i.test(filename)) return "xml";
79
- if (/\.css$/i.test(filename)) return "css";
80
- if (/\.s[ac]ss$/i.test(filename)) return "scss";
81
- if (/\.json$/i.test(filename)) return "json";
82
- return "plaintext";
83
- };
84
- var escapeHTML = (code) => code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
85
- var TAB_REPLACE_RE = /^(<[^>]+>|\t)+/gm;
86
- var normalizeHighlightTabs = (code) => code.replace(TAB_REPLACE_RE, (match) => match.replace(/\t/g, " "));
87
- var highlightCodeByLanguage = (code, language) => {
88
- try {
89
- const highlighted = registerVueHighlight().highlight(code, {
90
- language: language || "plaintext",
91
- ignoreIllegals: true
92
- }).value;
93
- return normalizeHighlightTabs(highlighted);
94
- } catch {
95
- return escapeHTML(code);
96
- }
97
- };
98
- var highlightCode = (code, filename) => highlightCodeByLanguage(code, resolveHighlightLanguage(filename));
99
- //#endregion
100
- //#region packages/playground/src/icon/icons.ts
101
- var strokeIcon = (path) => ({
102
- path,
103
- fill: "none",
104
- stroke: "currentColor"
105
- });
106
- var solidIcon = (path) => ({
107
- path,
108
- fill: "currentColor",
109
- stroke: "none"
110
- });
111
- var PLAYGROUND_ICONS = {
112
- runtime: strokeIcon("M5 3h14a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Zm3 18h8m-4-3v3m-2-13 5 3-5 3V8Z"),
113
- files: strokeIcon("M6 2h8l5 5v13a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Zm8 0v6h5m-9 5-2 2 2 2m5-4 2 2-2 2"),
114
- copy: strokeIcon(["M9 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2Z", "m-2 8H5a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v4"].join("")),
115
- refresh: solidIcon(["M12 2.5a9.5 9.5 0 1 1 0 19 1 1 0 1 1 0-2 7.5 7.5 0 1 0-7.5-7.8l.24-.23a.75.75 0 1 1 1.06 1.06", "L4.03 14.3a.75.75 0 0 1-1.06 0L1.2 12.53a.75.75 0 1 1 1.06-1.06l.24.24A9.5 9.5 0 0 1 12 2.5"].join("")),
116
- editor: solidIcon([
117
- "M9.38 19.32a.82.82 0 0 1-.71-1.23l5.85-10.13a.82.82 0 0 1 1.42.82l-5.85 10.13a.8.8 0 0 1-.71.4",
118
- "m-2.52-.76a.8.8 0 0 1-.58-.25l-4.13-4.12a1.06 1.06 0 0 1 0-1.5l4.13-4.12a.82.82 0 1 1 1.16 1.16",
119
- "l-3.72 3.71 3.72 3.71a.82.82 0 0 1-.58 1.4m10.9 0a.82.82 0 0 1-.58-1.4l3.7-3.7-3.7-3.72",
120
- "a.82.82 0 1 1 1.16-1.16l4.12 4.13c.41.4.41 1.08 0 1.49l-4.12 4.12a.8.8 0 0 1-.59.25"
121
- ].join("")),
122
- viewport: solidIcon([
123
- "M21.82 2.93c-.3-.3-.7-.47-1.1-.47H4.6c-.87 0-1.58.7-1.58 1.58v4.18h1.12V3.67h17v12.54",
124
- "c0 .25-.2.46-.45.46H9.49v1.13h.67q-.25.66-.6 1.11l-.06.06v1.86h7.86a.56.56 0 1 0 0-1.13",
125
- "q-1.46 0-2.2-1.9h5.55c.87 0 1.57-.71 1.57-1.59V4.04a1.6 1.6 0 0 0-.46-1.11M10.55 19.6",
126
- "c.43-.63.66-1.08.92-1.81h2.37c.25.8.51 1.26.97 1.77l.03.05zM8.12 8.4H2.9",
127
- "c-.64 0-1.17.53-1.17 1.19v10.75c0 .66.53 1.19 1.17 1.19h5.21c.65 0 1.18-.53 1.18-1.19V9.6",
128
- "a1.16 1.16 0 0 0-1.17-1.19M8.1 20.36H2.94V9.64h5.15z"
129
- ].join("")),
130
- expand: strokeIcon("M6 9l6 6 6-6"),
131
- popup: strokeIcon(["M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3", "M3 16v3a2 2 0 0 0 2 2h3"].join(""))
132
- };
133
- //#endregion
134
- //#region packages/playground/src/icon/icon.vue?vue&type=script&setup=true&lang.ts
135
- var _hoisted_1$8 = ["fill", "stroke"];
136
- var _hoisted_2$7 = ["d"];
137
- //#endregion
138
- //#region packages/playground/src/icon/icon.vue
139
- var icon_default = /* @__PURE__ */ (0, vue.defineComponent)({
140
- __name: "icon",
141
- props: {
142
- name: {},
143
- size: {}
144
- },
145
- setup(__props) {
146
- const props = __props;
147
- const icon = (0, vue.computed)(() => PLAYGROUND_ICONS[props.name]);
148
- const sizeStyle = (0, vue.computed)(() => {
149
- if (props.size == null) return;
150
- const value = typeof props.size === "number" ? `${props.size}px` : props.size;
151
- return {
152
- width: value,
153
- height: value
154
- };
155
- });
156
- return (_ctx, _cache) => {
157
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("svg", {
158
- class: "docs-playground-icon",
159
- viewBox: "0 0 24 24",
160
- style: (0, vue.normalizeStyle)(sizeStyle.value),
161
- fill: icon.value.fill,
162
- stroke: icon.value.stroke,
163
- "stroke-width": "1.8",
164
- "stroke-linecap": "round",
165
- "stroke-linejoin": "round",
166
- "aria-hidden": "true"
167
- }, [(0, vue.createElementVNode)("path", { d: icon.value.path }, null, 8, _hoisted_2$7)], 12, _hoisted_1$8);
168
- };
169
- }
170
- });
171
- //#endregion
172
- //#region packages/playground/src/core/code-preview/github.css?inline
173
- var github_default = "pre code.hljs{display:block;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{font-weight:700;color:#005cc5}.hljs-bullet{color:#735c0f}.hljs-emphasis{font-style:italic;color:#24292e}.hljs-strong{font-weight:700;color:#24292e}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}@media screen{[data-doc-theme=dark] .hljs,[data-vc-theme=dark] .hljs{color:#c9d1d9;background:#0d1117}[data-doc-theme=dark] .hljs-doctag,[data-doc-theme=dark] .hljs-keyword,[data-doc-theme=dark] .hljs-meta .hljs-keyword,[data-doc-theme=dark] .hljs-template-tag,[data-doc-theme=dark] .hljs-template-variable,[data-doc-theme=dark] .hljs-type,[data-doc-theme=dark] .hljs-variable.language_,[data-vc-theme=dark] .hljs-doctag,[data-vc-theme=dark] .hljs-keyword,[data-vc-theme=dark] .hljs-meta .hljs-keyword,[data-vc-theme=dark] .hljs-template-tag,[data-vc-theme=dark] .hljs-template-variable,[data-vc-theme=dark] .hljs-type,[data-vc-theme=dark] .hljs-variable.language_{color:#ff7b72}[data-doc-theme=dark] .hljs-title,[data-doc-theme=dark] .hljs-title.class_,[data-doc-theme=dark] .hljs-title.function_,[data-vc-theme=dark] .hljs-title,[data-vc-theme=dark] .hljs-title.class_,[data-vc-theme=dark] .hljs-title.function_{color:#d2a8ff}[data-doc-theme=dark] .hljs-attr,[data-doc-theme=dark] .hljs-literal,[data-doc-theme=dark] .hljs-number,[data-doc-theme=dark] .hljs-variable,[data-vc-theme=dark] .hljs-attr,[data-vc-theme=dark] .hljs-literal,[data-vc-theme=dark] .hljs-number,[data-vc-theme=dark] .hljs-variable{color:#79c0ff}[data-doc-theme=dark] .hljs-regexp,[data-doc-theme=dark] .hljs-string,[data-vc-theme=dark] .hljs-regexp,[data-vc-theme=dark] .hljs-string{color:#a5d6ff}[data-doc-theme=dark] .hljs-code,[data-doc-theme=dark] .hljs-comment,[data-vc-theme=dark] .hljs-code,[data-vc-theme=dark] .hljs-comment{color:#8b949e}[data-doc-theme=dark] .hljs-name,[data-doc-theme=dark] .hljs-selector-tag,[data-vc-theme=dark] .hljs-name,[data-vc-theme=dark] .hljs-selector-tag{color:#7ee787}}";
174
- //#endregion
175
- //#region packages/playground/src/core/code-preview/code-preview.scss?inline
176
- var code_preview_default$1 = ".docs-code-preview{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;min-height:0;overflow:hidden;background:var(--docs-background-color-soft,var(--vc-background-color,#f7f8fa));border-radius:8px}.docs-code-preview__language{z-index:1;min-width:28px;padding:0 8px;font-size:12px;line-height:18px;white-space:nowrap;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:opacity .15s;transition:opacity .15s}.docs-code-preview__copy,.docs-code-preview__language{position:absolute;top:6px;right:12px;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;height:28px;color:var(--docs-foreground-color-mute,var(--vc-color-dark-extralight,#71717a));-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.docs-code-preview__copy{z-index:2;width:28px;padding:0;pointer-events:none;cursor:pointer;background:var(--docs-background-color-soft,var(--vc-background-color,#f7f8fa));border:1px solid var(--docs-border-color,var(--vc-border-color,#d4d4d8));border-radius:8px;opacity:0;-webkit-transition:opacity .15s,background-color .15s;transition:opacity .15s,background-color .15s}.docs-code-preview__copy:hover{background:var(--docs-background-color,var(--vc-background-color-lightest,#fff))}.docs-code-preview__copy:focus-visible{pointer-events:auto;opacity:1}.docs-code-preview:has(.docs-code-preview__copy:focus-visible) .docs-code-preview__language,.docs-code-preview:hover .docs-code-preview__language{opacity:0}.docs-code-preview:has(.docs-code-preview__copy:focus-visible) .docs-code-preview__copy,.docs-code-preview:hover .docs-code-preview__copy{pointer-events:auto;opacity:1}.docs-code-preview__scroller{width:100%;height:100%;min-width:0;min-height:0;-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}.docs-code-preview__code{width:-webkit-max-content;width:-moz-max-content;width:max-content;margin:0}.docs-code-preview__code,.docs-code-preview__code code.hljs{min-width:100%;min-height:100%;overflow:visible;background:var(--docs-background-color-soft,var(--vc-background-color,#f7f8fa))}.docs-code-preview__code code.hljs{display:block;padding:12px 96px 12px 24px;-webkit-box-sizing:border-box;box-sizing:border-box}";
177
- //#endregion
178
- //#region packages/playground/src/core/code-preview/style.ts
179
- var CODE_PREVIEW_STYLE_ID = "docs-code-preview-style";
180
- var styleText = `${github_default}\n${code_preview_default$1}`;
181
- var ensureCodePreviewStyle = () => {
182
- if (typeof document === "undefined") return;
183
- let style = document.getElementById(CODE_PREVIEW_STYLE_ID);
184
- if (!style) {
185
- style = document.createElement("style");
186
- style.id = CODE_PREVIEW_STYLE_ID;
187
- document.head.appendChild(style);
188
- }
189
- if (style.textContent !== styleText) style.textContent = styleText;
190
- };
191
- //#endregion
192
- //#region packages/playground/src/core/code-preview/code-preview.vue?vue&type=script&setup=true&lang.ts
193
- var _hoisted_1$7 = { class: "docs-code-preview" };
194
- var _hoisted_2$6 = ["title"];
195
- var _hoisted_3$6 = { class: "docs-code-preview__code" };
196
- var _hoisted_4$6 = ["innerHTML"];
197
- //#endregion
198
- //#region packages/playground/src/core/code-preview/code-preview.vue
199
- var code_preview_default = /* @__PURE__ */ (0, vue.defineComponent)({
200
- __name: "code-preview",
201
- props: {
202
- code: {},
203
- filename: { default: "" },
204
- language: { default: "" },
205
- copyLabel: { default: void 0 },
206
- locale: {}
207
- },
208
- setup(__props) {
209
- ensureCodePreviewStyle();
210
- const props = __props;
211
- const { t } = (0, _deot_docs_locale.useLocale)((0, vue.computed)(() => props.locale));
212
- const resolvedCopyLabel = (0, vue.computed)(() => props.copyLabel || t("playground.common.copyCode"));
213
- const languageLabels = {
214
- javascript: "js",
215
- typescript: "ts",
216
- plaintext: "txt",
217
- xml: "html"
218
- };
219
- const filenameExtension = (0, vue.computed)(() => props.filename.split(".").at(-1)?.toLowerCase() || "");
220
- const languageLabel = (0, vue.computed)(() => {
221
- const language = props.language.toLowerCase();
222
- return languageLabels[language] || language || filenameExtension.value;
223
- });
224
- const highlightLanguage = (0, vue.computed)(() => props.language || resolveHighlightLanguage(props.filename));
225
- const highlightedCode = (0, vue.computed)(() => highlightCodeByLanguage(props.code, highlightLanguage.value));
226
- return (_ctx, _cache) => {
227
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$7, [
228
- languageLabel.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
229
- key: 0,
230
- class: "docs-code-preview__language",
231
- title: languageLabel.value
232
- }, (0, vue.toDisplayString)(languageLabel.value), 9, _hoisted_2$6)) : (0, vue.createCommentVNode)("v-if", true),
233
- (0, vue.createVNode)((0, vue.unref)(_deot_vc.Clipboard), {
234
- class: "docs-code-preview__copy",
235
- value: __props.code,
236
- tag: "button",
237
- type: "button",
238
- title: resolvedCopyLabel.value,
239
- "aria-label": resolvedCopyLabel.value
240
- }, {
241
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(icon_default), { name: "copy" })]),
242
- _: 1
243
- }, 8, [
244
- "value",
245
- "title",
246
- "aria-label"
247
- ]),
248
- (0, vue.createVNode)((0, vue.unref)(_deot_vc.Scroller), {
249
- class: "docs-code-preview__scroller",
250
- "auto-resize": true,
251
- native: false,
252
- "show-bar": true,
253
- height: "100%"
254
- }, {
255
- default: (0, vue.withCtx)(() => [(0, vue.createCommentVNode)(" eslint-disable-next-line vue/no-v-html "), (0, vue.createElementVNode)("pre", _hoisted_3$6, [(0, vue.createElementVNode)("code", {
256
- class: "hljs",
257
- innerHTML: highlightedCode.value
258
- }, null, 8, _hoisted_4$6)])]),
259
- _: 1
260
- })
261
- ]);
262
- };
263
- }
264
- });
265
- //#endregion
266
- //#region packages/playground/src/core/files-preview/files-preview.vue?vue&type=script&setup=true&lang.ts
267
- var _hoisted_1$6 = { class: "docs-playground-files" };
268
- var _hoisted_2$5 = { class: "docs-playground-files__tabs-row" };
269
- var _hoisted_3$5 = ["data-filename", "onClick"];
270
- var _hoisted_4$5 = {
271
- key: 0,
272
- class: "docs-playground-files__entry"
273
- };
274
- //#endregion
275
- //#region packages/playground/src/core/files-preview/files-preview.vue
276
- var files_preview_default = /* @__PURE__ */ (0, vue.defineComponent)({
277
- __name: "files-preview",
278
- props: {
279
- files: {},
280
- entry: {},
281
- views: {},
282
- activeView: {},
283
- activeFilename: {}
284
- },
285
- emits: ["active-change"],
286
- setup(__props, { emit: __emit }) {
287
- const props = __props;
288
- const { t } = (0, _deot_docs_locale.useLocale)();
289
- const emit = __emit;
290
- const filenames = (0, vue.computed)(() => Object.keys(props.files));
291
- const activeCode = (0, vue.computed)(() => props.files[props.activeFilename] || "");
292
- const handleActive = (filename) => {
293
- if (filename !== props.activeFilename) emit("active-change", filename);
294
- };
295
- return (_ctx, _cache) => {
296
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$6, [(0, vue.createElementVNode)("div", _hoisted_2$5, [(0, vue.createVNode)((0, vue.unref)(_deot_vc.Scroller), {
297
- class: "docs-playground-files__scroller",
298
- "auto-resize": true,
299
- native: false,
300
- "show-bar": true,
301
- height: "40",
302
- "content-class": "docs-playground-files__tabs",
303
- "content-style": "height: 40px; overflow: hidden;",
304
- "wrapper-style": "height: 40px; overflow-x: auto; overflow-y: hidden;"
305
- }, {
306
- default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(filenames.value, (filename) => {
307
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
308
- key: filename,
309
- type: "button",
310
- class: (0, vue.normalizeClass)(["docs-playground-files__tab", { "is-active": filename === __props.activeFilename }]),
311
- "data-filename": filename,
312
- onClick: ($event) => handleActive(filename)
313
- }, [(0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(filename), 1), filename === __props.entry ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("small", _hoisted_4$5, (0, vue.toDisplayString)((0, vue.unref)(t)("playground.files.entry")), 1)) : (0, vue.createCommentVNode)("v-if", true)], 10, _hoisted_3$5);
314
- }), 128))]),
315
- _: 1
316
- })]), (0, vue.createVNode)((0, vue.unref)(code_preview_default), {
317
- class: "docs-playground-files__body",
318
- code: activeCode.value,
319
- filename: __props.activeFilename,
320
- "copy-label": (0, vue.unref)(t)("playground.common.copyCurrentFile")
321
- }, null, 8, [
322
- "code",
323
- "filename",
324
- "copy-label"
325
- ])]);
326
- };
327
- }
328
- });
329
- //#endregion
330
- //#region packages/playground/src/editor/drag.ts
331
- var events = typeof document !== "undefined" && "ontouchstart" in document ? {
332
- start: "touchstart",
333
- move: "touchmove",
334
- end: "touchend"
335
- } : {
336
- start: "mousedown",
337
- move: "mousemove",
338
- end: "mouseup"
339
- };
340
- var DEFAULT_OFFSET = 10;
341
- var END_OFFSET = 0;
342
- var readPoint = (e) => {
343
- const touch = e.touches?.[0];
344
- if (touch) return touch;
345
- return {
346
- clientX: Number(e.clientX),
347
- clientY: Number(e.clientY)
348
- };
349
- };
350
- /**
351
- * 读取样式上已有的偏移量。
352
- * @param value 样式声明的 right / bottom 值。
353
- * @param fallback 样式缺省时使用的偏移量。
354
- * @returns 像素偏移量;auto 表示该方向由样式表接管,返回 null 以保留当前偏移。
355
- */
356
- var readOffset = (value, fallback) => {
357
- if (value === "auto") return null;
358
- const parsed = parseInt(value, 10);
359
- return Number.isNaN(parsed) ? fallback : parsed;
360
- };
361
- /**
362
- * 目前只针对right / bottom 做处理
363
- */
364
- var Drag = class {
365
- el;
366
- wrapper;
367
- container;
368
- right = 0;
369
- bottom = 0;
370
- maxRight;
371
- maxBottom;
372
- currentX = 0;
373
- currentY = 0;
374
- flag = false;
375
- constructor(options = {}) {
376
- const { el, wrapper, container } = options;
377
- if (!el) throw new Error("必传");
378
- this.el = el;
379
- this.wrapper = wrapper || el;
380
- this.container = container || window;
381
- const width = "innerWidth" in this.container ? this.container.innerWidth : this.container.clientWidth;
382
- const height = "innerHeight" in this.container ? this.container.innerHeight : this.container.clientHeight;
383
- this.maxRight = width - this.wrapper.offsetWidth;
384
- this.maxBottom = height - this.wrapper.offsetHeight;
385
- this.readOffsets(DEFAULT_OFFSET);
386
- this.handleStart = this.handleStart.bind(this);
387
- this.handleMove = this.handleMove.bind(this);
388
- this.handleEnd = this.handleEnd.bind(this);
389
- el.addEventListener(events.start, this.handleStart, false);
390
- }
391
- off() {
392
- const { el } = this;
393
- el.removeEventListener(events.start, this.handleStart, false);
394
- }
395
- readOffsets(fallback) {
396
- const right = readOffset(this.wrapper.style.right, fallback);
397
- const bottom = readOffset(this.wrapper.style.bottom, fallback);
398
- if (right !== null) this.right = right;
399
- if (bottom !== null) this.bottom = bottom;
400
- }
401
- operateDOMEvents(type) {
402
- if (type === "remove") {
403
- document.removeEventListener(events.move, this.handleMove, false);
404
- document.removeEventListener(events.end, this.handleEnd, false);
405
- return;
406
- }
407
- document.addEventListener(events.move, this.handleMove, false);
408
- document.addEventListener(events.end, this.handleEnd, false);
409
- }
410
- handleStart(e) {
411
- this.flag = true;
412
- e.preventDefault();
413
- const point = readPoint(e);
414
- this.currentX = point.clientX;
415
- this.currentY = point.clientY;
416
- this.operateDOMEvents("add");
417
- }
418
- handleMove(e) {
419
- e.preventDefault();
420
- const point = readPoint(e);
421
- if (!this.flag) return;
422
- const disX = point.clientX - this.currentX;
423
- const disY = point.clientY - this.currentY;
424
- let nowRight = this.right - disX;
425
- let nowBottom = this.bottom - disY;
426
- nowRight < 0 && (nowRight = 0);
427
- nowBottom < 0 && (nowBottom = 0);
428
- nowRight > this.maxRight && (nowRight = this.maxRight);
429
- nowBottom > this.maxBottom && (nowBottom = this.maxBottom);
430
- this.wrapper.style.right = nowRight + "px";
431
- this.wrapper.style.bottom = nowBottom + "px";
432
- }
433
- handleEnd() {
434
- this.flag = false;
435
- this.readOffsets(END_OFFSET);
436
- this.operateDOMEvents("remove");
437
- }
438
- };
439
- //#endregion
440
- //#region packages/playground/src/editor/editor.vue?vue&type=script&setup=true&lang.ts
441
- var _hoisted_1$5 = { class: "docs-playground-editor" };
442
- var _hoisted_2$4 = ["title"];
443
- var _hoisted_3$4 = { class: "docs-playground-editor__files" };
444
- var _hoisted_4$4 = [
445
- "data-filename",
446
- "onClick",
447
- "onDblclick"
448
- ];
449
- var _hoisted_5$4 = ["onKeydown"];
450
- var _hoisted_6$3 = ["title"];
451
- var _hoisted_7$3 = ["title", "aria-label"];
452
- var _hoisted_8$2 = ["title", "aria-label"];
453
- var _hoisted_9$1 = { class: "docs-playground-editor__actions" };
454
- var _hoisted_10$1 = ["title"];
455
- var _hoisted_11$1 = ["title", "disabled"];
456
- var _hoisted_12$1 = {
457
- key: 0,
458
- class: "docs-playground-editor__error"
459
- };
460
- var EDITOR_SCROLLER_MAX_HEIGHT = "calc(100vh - 110px)";
461
- //#endregion
462
- //#region packages/playground/src/editor/editor.vue
463
- var editor_default = /* @__PURE__ */ (0, vue.defineComponent)({
464
- __name: "editor",
465
- props: {
466
- value: { default: "" },
467
- files: { default: () => ({}) },
468
- entry: { default: "" },
469
- onChange: {
470
- type: Function,
471
- default: () => {}
472
- },
473
- onFilesChange: {
474
- type: Function,
475
- default: () => {}
476
- },
477
- onActiveChange: {
478
- type: Function,
479
- default: () => {}
480
- },
481
- getCodeErrors: {
482
- type: Function,
483
- default: () => []
484
- },
485
- locale: {}
486
- },
487
- setup(__props) {
488
- const SUPPORTED_FILE_RE = /\.(vue|js|ts|jsx|tsx|css|s[ac]ss|json|html)$/i;
489
- /** 浮层留白 20px + 标题栏 44px + 文件栏 46px,避免大文件把标签栏挤扁。 */
490
- const props = __props;
491
- const { t } = (0, _deot_docs_locale.useLocale)((0, vue.computed)(() => props.locale));
492
- const initialFiles = Object.keys(props.files).length ? props.files : { "App.vue": props.value };
493
- const editorFiles = (0, vue.ref)({ ...initialFiles });
494
- const filenames = (0, vue.computed)(() => Object.keys(editorFiles.value));
495
- const currentEntry = (0, vue.ref)(props.entry && editorFiles.value[props.entry] !== void 0 ? props.entry : filenames.value[0]);
496
- const activeFilename = (0, vue.ref)(currentEntry.value);
497
- const isActive = (0, vue.ref)(false);
498
- const textarea = (0, vue.ref)(null);
499
- const filenameInput = (0, vue.ref)(null);
500
- const scroller = (0, vue.ref)(null);
501
- const editorScroller = (0, vue.ref)(null);
502
- const editor = (0, vue.ref)(null);
503
- const bar = (0, vue.ref)(null);
504
- const wrapper = (0, vue.ref)(null);
505
- const drag = (0, vue.ref)(null);
506
- const renamingFilename = (0, vue.ref)("");
507
- const filenameDraft = (0, vue.ref)("");
508
- const codeErrorText = (0, vue.computed)(() => props.getCodeErrors().map((item) => item instanceof Error ? item.message : item).join("\n"));
509
- let editorResize;
510
- const refreshEditorScroller = () => {
511
- editorScroller.value?.refresh();
512
- };
513
- const setFilenameInput = (el) => {
514
- filenameInput.value = el instanceof HTMLInputElement ? el : null;
515
- };
516
- const handleHide = () => {
517
- isActive.value = false;
518
- };
519
- const languageExtension = (filename) => {
520
- if (/\.vue$/i.test(filename)) return (0, _codemirror_lang_vue.vue)();
521
- if (/\.[jt]sx?$/i.test(filename)) return (0, _codemirror_lang_javascript.javascript)({
522
- jsx: /x$/i.test(filename),
523
- typescript: /\.tsx?$/i.test(filename)
524
- });
525
- if (/\.(css|s[ac]ss)$/i.test(filename)) return (0, _codemirror_lang_css.css)();
526
- };
527
- const createEditor = () => {
528
- if (!textarea.value || !activeFilename.value) return;
529
- editor.value?.destroy();
530
- const filename = activeFilename.value;
531
- const language = languageExtension(filename);
532
- editor.value = new codemirror.EditorView({
533
- doc: editorFiles.value[filename] || "",
534
- extensions: [
535
- codemirror.basicSetup,
536
- ...language ? [language] : [],
537
- (0, _codemirror_view.highlightActiveLine)(),
538
- codemirror.EditorView.updateListener.of((e) => {
539
- if (!e.docChanged) return;
540
- const code = e.state.doc.toString();
541
- editorFiles.value = {
542
- ...editorFiles.value,
543
- [filename]: code
544
- };
545
- props.onFilesChange({ ...editorFiles.value }, currentEntry.value, {
546
- type: "update",
547
- filename
548
- });
549
- if (filename === currentEntry.value) props.onChange(code);
550
- (0, vue.nextTick)(refreshEditorScroller);
551
- })
552
- ],
553
- parent: textarea.value
554
- });
555
- editor.value.focus();
556
- (0, vue.nextTick)(refreshEditorScroller);
557
- };
558
- const handleActive = (filename) => {
559
- if (filename === activeFilename.value || editorFiles.value[filename] === void 0) return;
560
- activeFilename.value = filename;
561
- props.onActiveChange(filename);
562
- createEditor();
563
- };
564
- const handleTabClick = (e, filename) => {
565
- if (e.target.closest(".docs-playground-editor__close")) return;
566
- handleActive(filename);
567
- };
568
- const nextFilename = () => {
569
- let index = 1;
570
- let filename = "comp1.vue";
571
- while (editorFiles.value[filename] !== void 0) {
572
- index++;
573
- filename = `comp${index}.vue`;
574
- }
575
- return filename;
576
- };
577
- const focusFilename = () => (0, vue.nextTick)(() => {
578
- filenameInput.value?.focus();
579
- filenameInput.value?.select();
580
- });
581
- const handleCreate = () => {
582
- const filename = nextFilename();
583
- editorFiles.value = {
584
- ...editorFiles.value,
585
- [filename]: require_css.NEW_SFC_CODE
586
- };
587
- activeFilename.value = filename;
588
- notifyFilesChange({
589
- type: "create",
590
- filename
591
- });
592
- props.onActiveChange(filename);
593
- createEditor();
594
- (0, vue.nextTick)(async () => {
595
- await scroller.value?.refresh();
596
- scroller.value?.setScrollLeft(Number.MAX_SAFE_INTEGER);
597
- });
598
- };
599
- const handleRename = (filename) => {
600
- handleActive(filename);
601
- renamingFilename.value = filename;
602
- filenameDraft.value = filename;
603
- focusFilename();
604
- };
605
- const handleRenameCancel = () => {
606
- renamingFilename.value = "";
607
- filenameDraft.value = "";
608
- };
609
- const validateFilename = (filename, previousFilename = "") => {
610
- if (!filename) return t("playground.validation.filenameRequired");
611
- if (filename.startsWith("/") || filename.includes("\\")) return t("playground.validation.filenameRelative");
612
- if (filename.split("/").some((part) => !part || part === "." || part === "..")) return t("playground.validation.filenameSegments");
613
- if (!SUPPORTED_FILE_RE.test(filename)) return t("playground.validation.fileTypeUnsupported");
614
- if (filename !== previousFilename && editorFiles.value[filename] !== void 0) return t("playground.validation.filenameExists");
615
- return "";
616
- };
617
- const notifyFilesChange = (action) => {
618
- props.onFilesChange({ ...editorFiles.value }, currentEntry.value, action);
619
- };
620
- const handleRenameSubmit = () => {
621
- const filename = filenameDraft.value.trim();
622
- const previousFilename = renamingFilename.value;
623
- if (!previousFilename) return;
624
- const operationError = validateFilename(filename, previousFilename);
625
- if (operationError) {
626
- _deot_vc.Message.error(operationError);
627
- renamingFilename.value = "";
628
- filenameDraft.value = "";
629
- return;
630
- }
631
- if (filename !== previousFilename) {
632
- const renamedFiles = {};
633
- for (const [name, code] of Object.entries(editorFiles.value)) renamedFiles[name === previousFilename ? filename : name] = code;
634
- editorFiles.value = renamedFiles;
635
- activeFilename.value = filename;
636
- if (currentEntry.value === previousFilename) currentEntry.value = filename;
637
- notifyFilesChange({
638
- type: "rename",
639
- filename,
640
- previousFilename
641
- });
642
- }
643
- renamingFilename.value = "";
644
- filenameDraft.value = "";
645
- createEditor();
646
- };
647
- const handleDelete = (filename) => {
648
- if (filename === currentEntry.value) return;
649
- const nextFiles = { ...editorFiles.value };
650
- delete nextFiles[filename];
651
- editorFiles.value = nextFiles;
652
- notifyFilesChange({
653
- type: "delete",
654
- filename
655
- });
656
- if (activeFilename.value === filename) {
657
- activeFilename.value = currentEntry.value;
658
- props.onActiveChange(currentEntry.value);
659
- createEditor();
660
- }
661
- };
662
- const handleEntry = () => {
663
- if (activeFilename.value === currentEntry.value) return;
664
- currentEntry.value = activeFilename.value;
665
- notifyFilesChange({
666
- type: "entry",
667
- filename: currentEntry.value
668
- });
669
- };
670
- (0, vue.onMounted)(async () => {
671
- isActive.value = true;
672
- await (0, vue.nextTick)();
673
- if (!textarea.value || !bar.value || !wrapper.value) return;
674
- createEditor();
675
- drag.value = new Drag({
676
- el: bar.value,
677
- wrapper: wrapper.value,
678
- container: window
679
- });
680
- if (typeof ResizeObserver !== "undefined") {
681
- editorResize = new ResizeObserver(refreshEditorScroller);
682
- editorResize.observe(textarea.value);
683
- }
684
- });
685
- (0, vue.onBeforeUnmount)(() => {
686
- editorResize?.disconnect();
687
- drag.value?.off();
688
- editor.value?.destroy();
689
- });
690
- return (_ctx, _cache) => {
691
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$5, [(0, vue.createVNode)((0, vue.unref)(_deot_vc.TransitionFade), { onAfterLeave: handleHide }, {
692
- default: (0, vue.withCtx)(() => [(0, vue.withDirectives)((0, vue.createElementVNode)("div", {
693
- ref_key: "wrapper",
694
- ref: wrapper,
695
- class: "docs-playground-editor__wrapper"
696
- }, [
697
- (0, vue.createElementVNode)("div", {
698
- ref_key: "bar",
699
- ref: bar,
700
- class: "docs-playground-editor__header"
701
- }, [(0, vue.createVNode)((0, vue.unref)(icon_default), {
702
- name: "editor",
703
- size: 24
704
- }), (0, vue.createElementVNode)("span", {
705
- title: (0, vue.unref)(t)("playground.common.close"),
706
- style: { "cursor": "pointer" },
707
- onClick: handleHide
708
- }, "✕", 8, _hoisted_2$4)], 512),
709
- (0, vue.createElementVNode)("div", _hoisted_3$4, [(0, vue.createVNode)((0, vue.unref)(_deot_vc.Scroller), {
710
- ref_key: "scroller",
711
- ref: scroller,
712
- class: "docs-playground-editor__scroller",
713
- "auto-resize": true,
714
- native: false,
715
- "show-bar": true,
716
- height: "40",
717
- "content-class": "docs-playground-editor__tabs",
718
- "wrapper-style": "overflow-y: hidden;"
719
- }, {
720
- default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(filenames.value, (filename) => {
721
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
722
- key: filename,
723
- class: (0, vue.normalizeClass)(["docs-playground-editor__tab", { "is-active": filename === activeFilename.value }]),
724
- "data-filename": filename,
725
- onClick: ($event) => handleTabClick($event, filename),
726
- onDblclick: ($event) => handleRename(filename)
727
- }, [renamingFilename.value === filename ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("input", {
728
- key: 0,
729
- ref_for: true,
730
- ref: setFilenameInput,
731
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => filenameDraft.value = $event),
732
- class: "docs-playground-editor__filename",
733
- onClick: _cache[1] || (_cache[1] = (0, vue.withModifiers)(() => {}, ["stop"])),
734
- onDblclick: _cache[2] || (_cache[2] = (0, vue.withModifiers)(() => {}, ["stop"])),
735
- onBlur: handleRenameSubmit,
736
- onKeydown: [(0, vue.withKeys)((0, vue.withModifiers)(handleRenameSubmit, ["prevent"]), ["enter"]), (0, vue.withKeys)((0, vue.withModifiers)(handleRenameCancel, ["prevent"]), ["esc"])]
737
- }, null, 40, _hoisted_5$4)), [[vue.vModelText, filenameDraft.value]]) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [
738
- filename === currentEntry.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
739
- key: 0,
740
- title: (0, vue.unref)(t)("playground.editor.entry")
741
- }, "●", 8, _hoisted_6$3)) : (0, vue.createCommentVNode)("v-if", true),
742
- (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(filename), 1),
743
- filename === currentEntry.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
744
- key: 1,
745
- type: "button",
746
- class: "docs-playground-editor__close",
747
- title: (0, vue.unref)(t)("playground.editor.entryCannotDelete"),
748
- "aria-label": (0, vue.unref)(t)("playground.editor.entryCannotDelete"),
749
- disabled: ""
750
- }, null, 8, _hoisted_7$3)) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_deot_vc.Popconfirm), {
751
- key: 2,
752
- class: "docs-playground-editor__delete",
753
- title: (0, vue.unref)(t)("playground.editor.confirmDelete", { filename }),
754
- portal: true,
755
- placement: "bottom",
756
- onOk: ($event) => handleDelete(filename)
757
- }, {
758
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("button", {
759
- type: "button",
760
- class: "docs-playground-editor__close",
761
- title: (0, vue.unref)(t)("playground.editor.deleteFile"),
762
- "aria-label": (0, vue.unref)(t)("playground.editor.confirmDelete", { filename })
763
- }, null, 8, _hoisted_8$2)]),
764
- _: 2
765
- }, 1032, ["title", "onOk"]))
766
- ], 64))], 42, _hoisted_4$4);
767
- }), 128))]),
768
- _: 1
769
- }, 512), (0, vue.createElementVNode)("div", _hoisted_9$1, [(0, vue.createElementVNode)("button", {
770
- type: "button",
771
- title: (0, vue.unref)(t)("playground.editor.createFile"),
772
- onClick: handleCreate
773
- }, "+", 8, _hoisted_10$1), (0, vue.createElementVNode)("button", {
774
- type: "button",
775
- title: (0, vue.unref)(t)("playground.editor.setEntry"),
776
- disabled: activeFilename.value === currentEntry.value,
777
- onClick: handleEntry
778
- }, (0, vue.toDisplayString)((0, vue.unref)(t)("playground.editor.setEntry")), 9, _hoisted_11$1)])]),
779
- codeErrorText.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_12$1, (0, vue.toDisplayString)(codeErrorText.value), 1)) : (0, vue.createCommentVNode)("v-if", true),
780
- (0, vue.createVNode)((0, vue.unref)(_deot_vc.Scroller), {
781
- ref_key: "editorScroller",
782
- ref: editorScroller,
783
- class: "docs-playground-editor__body",
784
- "auto-resize": true,
785
- native: false,
786
- "show-bar": true,
787
- always: true,
788
- "max-height": EDITOR_SCROLLER_MAX_HEIGHT
789
- }, {
790
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", {
791
- ref_key: "textarea",
792
- ref: textarea,
793
- class: "docs-playground-editor__editor"
794
- }, null, 512)]),
795
- _: 1
796
- }, 512)
797
- ], 512), [[vue.vShow, isActive.value]])]),
798
- _: 1
799
- })]);
800
- };
801
- }
802
- });
803
- //#endregion
804
- //#region packages/playground/src/editor/index.ts
805
- var Editor = new _deot_vc.Portal(editor_default, { aliveRegExp: { className: /(PORTAL_TAG_DISABLE)/ } });
806
- //#endregion
807
- //#region packages/playground/src/utils.ts
808
- /**
809
- * 比较两组 playground 文件是否完全一致。
810
- * @param left 左侧文件表。
811
- * @param right 右侧文件表。
812
- * @returns 键集合与各文件内容是否都相同。
813
- */
814
- var filesEqual = (left, right) => {
815
- const leftKeys = Object.keys(left);
816
- const rightKeys = Object.keys(right);
817
- return leftKeys.length === rightKeys.length && leftKeys.every((key) => left[key] === right[key]);
818
- };
819
- /**
820
- * 视图切换条使用的文案键。
821
- * @param view 当前预览视图。
822
- * @returns locale 消息键。
823
- */
824
- var playgroundViewMessage = (view) => view === "runtime" ? "playground.runtime.preview" : "playground.runtime.files";
825
- /**
826
- * 与 markdown-it-anchor 默认 `slugify` 对齐,用于标题锚点 id。
827
- * @param title 标题文本。
828
- * @returns 可用作元素 id / hash 的字符串;空标题返回空串。
829
- */
830
- var slugifyPlaygroundTitle = (title) => {
831
- const normalized = String(title).trim().toLowerCase().replace(/\s+/g, "-");
832
- return normalized ? encodeURIComponent(normalized) : "";
833
- };
834
- /**
835
- * 解析标题锚点 id:显式 id 优先,否则从标题 slugify;必要时避开已占用 id。
836
- * @param title 标题文本。
837
- * @param explicitId 可选显式 id。
838
- * @param isOccupied 返回该 id 是否已被其他元素占用。
839
- * @returns 锚点 id;无标题时为空串。
840
- */
841
- var resolvePlaygroundTitleId = (title, explicitId = "", isOccupied) => {
842
- if (!String(title).trim()) return "";
843
- const base = String(explicitId).trim() || slugifyPlaygroundTitle(title);
844
- if (!base) return "";
845
- if (!isOccupied || !isOccupied(base)) return base;
846
- let index = 1;
847
- while (isOccupied(`${base}-${index}`)) index += 1;
848
- return `${base}-${index}`;
849
- };
850
- //#endregion
851
- //#region packages/playground/src/core/preview-scroll.ts
852
- /** iframe 包装成功后挂在 `html` 上,供样式与 auto-height 识别。 */
853
- var PREVIEW_SCROLL_HTML_CLASS = "docs-playground-preview-scroll";
854
- /** Scroller 内容节点 class,auto-height 优先测量该节点。 */
855
- var PREVIEW_SCROLL_CONTENT_CLASS = "docs-playground-preview-scroll__content";
856
- /** 重评时先清掉 html class,避免 `await import` 期间仍套用 `height:100%`。 */
857
- var PREVIEW_SCROLL_RESET_CODE = `document.documentElement.classList.remove("${PREVIEW_SCROLL_HTML_CLASS}");`;
858
- /** 动态探测 `@deot/vc`;失败时为空对象,不阻断 DocsLink / 预览挂载。 */
859
- var PREVIEW_SCROLLER_IMPORT_CODE = "const __docsVc=await import(\"@deot/vc\").catch(()=>({}))";
860
- /**
861
- * Vue 的 `mount` 闭包的是 `createApp(root)` 时的根组件,改 `app._component` 不会生效。
862
- * 因此劫持 `app.mount`:等全部 useCode 注册完后,再 `createApp` 包一层 Scroller。
863
- * 前导分号避免接在 `app.component(...)` 后被 ASI 解析成调用返回值。
864
- */
865
- var PREVIEW_SCROLLER_USE_CODE = [
866
- ";(()=>{",
867
- PREVIEW_SCROLL_RESET_CODE,
868
- "const __DocsScroller=__docsVc&&__docsVc.Scroller;",
869
- "if(!__DocsScroller)return;",
870
- "const UserApp=app._component;",
871
- "app.mount=(container,...rest)=>{",
872
- "const next=_createApp({",
873
- "name:\"DocsPreviewRoot\",",
874
- "setup(){return()=>__docsH(__DocsScroller,{",
875
- "height:\"100%\",",
876
- "native:false,",
877
- "autoResize:true,",
878
- "showBar:true,",
879
- `contentClass:"${PREVIEW_SCROLL_CONTENT_CLASS}"`,
880
- "},{default:()=>__docsH(UserApp)})}",
881
- "});",
882
- "next.config.errorHandler=app.config.errorHandler;",
883
- "if(app._context&&next._context){",
884
- "if(app._context.components)Object.assign(next._context.components,app._context.components);",
885
- "if(app._context.directives)Object.assign(next._context.directives,app._context.directives);",
886
- "Object.assign(next._context.provides,app._context.provides);",
887
- "next._context.mixins.push(...app._context.mixins);",
888
- "}",
889
- "window.__app__=next;",
890
- `document.documentElement.classList.add("${PREVIEW_SCROLL_HTML_CLASS}");`,
891
- "return next.mount(container,...rest)",
892
- "}",
893
- "})()"
894
- ].join("");
895
- var PREVIEW_SCROLLER_STYLE = [
896
- `html.${PREVIEW_SCROLL_HTML_CLASS},`,
897
- `html.${PREVIEW_SCROLL_HTML_CLASS} body,`,
898
- `html.${PREVIEW_SCROLL_HTML_CLASS} #app{`,
899
- "height:100%;min-height:100%;margin:0;overflow:hidden",
900
- "}",
901
- `html.${PREVIEW_SCROLL_HTML_CLASS} #app>.vc-scroller{`,
902
- "height:100%;max-height:100%;box-sizing:border-box",
903
- "}"
904
- ].join("");
905
- var resolveSandboxContainer = (sandbox) => sandbox?.container ? (0, vue.unref)(sandbox.container) : null;
906
- var resolveSandboxMessageSource = (sandbox) => {
907
- const exposed = sandbox?.messageSource ? (0, vue.unref)(sandbox.messageSource) : null;
908
- if (exposed && typeof exposed.postMessage === "function") return exposed;
909
- return (resolveSandboxContainer(sandbox)?.querySelector("iframe"))?.contentWindow ?? null;
910
- };
911
- /**
912
- * iframe 的 contentWindow 在 jsdom 里可能是 null,仍要和旧的元素校验一致。
913
- * 直接渲染会暴露真实的 messageSource。
914
- * @param sandbox
915
- * @param event
916
- */
917
- var isSandboxMessage = (sandbox, event) => {
918
- const explicit = sandbox?.messageSource ? (0, vue.unref)(sandbox.messageSource) : null;
919
- if (explicit && typeof explicit.postMessage === "function") return event.source === explicit;
920
- const iframe = resolveSandboxContainer(sandbox)?.querySelector("iframe");
921
- return !!iframe && event.source === iframe.contentWindow;
922
- };
923
- var resolveScrollContent = (iframeDocument) => {
924
- if (!iframeDocument.documentElement.classList.contains("docs-playground-preview-scroll")) return null;
925
- return iframeDocument.querySelector(`.${PREVIEW_SCROLL_CONTENT_CLASS}`);
926
- };
927
- var useSandboxAutoHeight = (sandboxRef) => {
928
- const height = (0, vue.ref)(24);
929
- let container = null;
930
- let iframe = null;
931
- let containerObserver = null;
932
- let contentObserver = null;
933
- let scrollMountObserver = null;
934
- let observedScrollNode = null;
935
- let frameId = 0;
936
- const cancelMeasure = () => {
937
- if (!frameId) return;
938
- cancelAnimationFrame(frameId);
939
- frameId = 0;
940
- };
941
- const measureDocumentContent = (iframeWindow, iframeDocument) => {
942
- const { body, documentElement } = iframeDocument;
943
- const bodyStyle = iframeWindow.getComputedStyle(body);
944
- const bodyMargins = (Number.parseFloat(bodyStyle.marginTop) || 0) + (Number.parseFloat(bodyStyle.marginBottom) || 0);
945
- const rootTop = documentElement.getBoundingClientRect().top;
946
- const bodyRect = body.getBoundingClientRect();
947
- const collapsedTop = Math.max(0, bodyRect.top - rootTop);
948
- let childrenBottom = 0;
949
- for (const child of body.children) {
950
- if (iframeWindow.getComputedStyle(child).position === "fixed") continue;
951
- const rect = child.getBoundingClientRect();
952
- const marginBottom = Number.parseFloat(iframeWindow.getComputedStyle(child).marginBottom) || 0;
953
- childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
954
- }
955
- const scrollHeight = Math.max(body.scrollHeight, documentElement.scrollHeight);
956
- const overflowingHeight = scrollHeight > (iframe?.clientHeight || 0) + 1 ? scrollHeight : 0;
957
- return Math.ceil(Math.max(body.offsetHeight + bodyMargins + collapsedTop, bodyRect.height + bodyMargins + collapsedTop, childrenBottom, overflowingHeight));
958
- };
959
- const measureScrollContent = (scrollContent) => {
960
- const contentStyle = scrollContent.ownerDocument.defaultView?.getComputedStyle(scrollContent);
961
- const margins = contentStyle ? (Number.parseFloat(contentStyle.marginTop) || 0) + (Number.parseFloat(contentStyle.marginBottom) || 0) : 0;
962
- let childrenBottom = 0;
963
- const rootTop = scrollContent.getBoundingClientRect().top;
964
- for (const child of scrollContent.children) {
965
- const rect = child.getBoundingClientRect();
966
- const marginBottom = Number.parseFloat(scrollContent.ownerDocument.defaultView?.getComputedStyle(child).marginBottom || "0") || 0;
967
- childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
968
- }
969
- return Math.ceil(Math.max(scrollContent.scrollHeight + margins, scrollContent.offsetHeight + margins, childrenBottom + margins));
970
- };
971
- const isPendingPreviewApp = (iframeDocument) => {
972
- const app = iframeDocument.getElementById("app");
973
- return app ? app.childElementCount === 0 : !!iframe?.srcdoc;
974
- };
975
- const releaseBootstrapHeight = (target) => {
976
- (0, vue.nextTick)(() => {
977
- if (iframe === target) target.style.height = "100%";
978
- });
979
- };
980
- const measureElementChildren = (root) => {
981
- const view = root.ownerDocument.defaultView || window;
982
- const rootTop = root.getBoundingClientRect().top;
983
- let childrenBottom = 0;
984
- for (const child of root.children) {
985
- if (view.getComputedStyle(child).position === "fixed") continue;
986
- const rect = child.getBoundingClientRect();
987
- const marginBottom = Number.parseFloat(view.getComputedStyle(child).marginBottom) || 0;
988
- childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
989
- }
990
- const overflowingHeight = root.scrollHeight > root.clientHeight + 1 ? root.scrollHeight : 0;
991
- const naturalHeight = root.classList.contains("docs-playground-local__mount--auto") ? root.offsetHeight : 0;
992
- return Math.ceil(Math.max(childrenBottom, overflowingHeight, naturalHeight));
993
- };
994
- const measureLocalContainer = () => {
995
- if (!container) return;
996
- const scrollContent = container.querySelector(`.${PREVIEW_SCROLL_CONTENT_CLASS}`);
997
- const mount = container.querySelector(".docs-playground-local__mount");
998
- const target = scrollContent || mount;
999
- if (!target || !scrollContent && target.childElementCount === 0) return;
1000
- const contentHeight = scrollContent ? measureScrollContent(scrollContent) : measureElementChildren(target);
1001
- const nextHeight = Math.max(contentHeight, 24);
1002
- if (height.value !== nextHeight) height.value = nextHeight;
1003
- };
1004
- const isLocalContainer = (element) => element?.classList.contains("docs-playground-local") === true;
1005
- const measure = () => {
1006
- frameId = 0;
1007
- if (isLocalContainer(container)) {
1008
- measureLocalContainer();
1009
- return;
1010
- }
1011
- if (!iframe) return;
1012
- try {
1013
- const iframeWindow = iframe.contentWindow;
1014
- const iframeDocument = iframe.contentDocument;
1015
- if (!iframeWindow || !iframeDocument?.body) return;
1016
- const scrollContent = resolveScrollContent(iframeDocument);
1017
- if (!scrollContent && isPendingPreviewApp(iframeDocument)) return;
1018
- const contentHeight = scrollContent ? measureScrollContent(scrollContent) : measureDocumentContent(iframeWindow, iframeDocument);
1019
- const nextHeight = Math.max(contentHeight, 24);
1020
- if (height.value !== nextHeight) height.value = nextHeight;
1021
- releaseBootstrapHeight(iframe);
1022
- } catch {
1023
- if (height.value !== 24) height.value = 24;
1024
- }
1025
- };
1026
- const scheduleMeasure = () => {
1027
- if (frameId) return;
1028
- frameId = requestAnimationFrame(measure);
1029
- };
1030
- const disconnectContent = () => {
1031
- contentObserver?.disconnect();
1032
- contentObserver = null;
1033
- scrollMountObserver?.disconnect();
1034
- scrollMountObserver = null;
1035
- observedScrollNode = null;
1036
- cancelMeasure();
1037
- };
1038
- const observeContent = () => {
1039
- disconnectContent();
1040
- if (!iframe) return;
1041
- try {
1042
- const iframeWindow = iframe.contentWindow;
1043
- const iframeDocument = iframe.contentDocument;
1044
- const Observer = iframeWindow?.ResizeObserver;
1045
- if (!iframeWindow || !iframeDocument?.body || !Observer) {
1046
- scheduleMeasure();
1047
- return;
1048
- }
1049
- const observer = new Observer(scheduleMeasure);
1050
- observer.observe(iframeDocument.documentElement);
1051
- observer.observe(iframeDocument.body);
1052
- contentObserver = observer;
1053
- const attachScrollContent = (node) => {
1054
- if (!node || !contentObserver) return false;
1055
- if (observedScrollNode === node) return true;
1056
- if (observedScrollNode) contentObserver.unobserve(observedScrollNode);
1057
- observedScrollNode = node;
1058
- contentObserver.observe(node);
1059
- return true;
1060
- };
1061
- const syncScrollContent = () => {
1062
- attachScrollContent(resolveScrollContent(iframeDocument));
1063
- scheduleMeasure();
1064
- };
1065
- scrollMountObserver = new MutationObserver(syncScrollContent);
1066
- scrollMountObserver.observe(iframeDocument.documentElement, {
1067
- attributes: true,
1068
- attributeFilter: ["class"]
1069
- });
1070
- scrollMountObserver.observe(iframeDocument.body, {
1071
- childList: true,
1072
- subtree: true
1073
- });
1074
- syncScrollContent();
1075
- } catch {
1076
- if (height.value !== 24) height.value = 24;
1077
- }
1078
- };
1079
- const handleIframeLoad = () => observeContent();
1080
- const setIframe = (nextIframe) => {
1081
- if (iframe === nextIframe) return;
1082
- iframe?.removeEventListener("load", handleIframeLoad);
1083
- disconnectContent();
1084
- iframe = nextIframe;
1085
- if (!iframe) {
1086
- height.value = 24;
1087
- return;
1088
- }
1089
- iframe.addEventListener("load", handleIframeLoad);
1090
- iframe.style.height = `${Math.max(window.innerHeight, 24)}px`;
1091
- observeContent();
1092
- };
1093
- const observeLocal = () => {
1094
- disconnectContent();
1095
- if (!container || typeof ResizeObserver === "undefined") {
1096
- scheduleMeasure();
1097
- return;
1098
- }
1099
- const observer = new ResizeObserver(scheduleMeasure);
1100
- observer.observe(container);
1101
- const mount = container.querySelector(".docs-playground-local__mount");
1102
- if (mount) observer.observe(mount);
1103
- contentObserver = observer;
1104
- scrollMountObserver = new MutationObserver(() => {
1105
- const nextMount = container?.querySelector(".docs-playground-local__mount");
1106
- if (nextMount && contentObserver) {
1107
- contentObserver.observe(nextMount);
1108
- for (const child of nextMount.children) contentObserver.observe(child);
1109
- }
1110
- scheduleMeasure();
1111
- });
1112
- scrollMountObserver.observe(container, {
1113
- childList: true,
1114
- subtree: true
1115
- });
1116
- scheduleMeasure();
1117
- };
1118
- const syncIframe = () => {
1119
- if (isLocalContainer(container)) {
1120
- iframe?.removeEventListener("load", handleIframeLoad);
1121
- iframe = null;
1122
- observeLocal();
1123
- return;
1124
- }
1125
- setIframe(container?.querySelector("iframe") || null);
1126
- };
1127
- const setContainer = (nextContainer) => {
1128
- if (container === nextContainer) return;
1129
- containerObserver?.disconnect();
1130
- containerObserver = null;
1131
- disconnectContent();
1132
- iframe?.removeEventListener("load", handleIframeLoad);
1133
- iframe = null;
1134
- container = nextContainer;
1135
- if (!container) {
1136
- height.value = 24;
1137
- return;
1138
- }
1139
- if (isLocalContainer(container)) {
1140
- observeLocal();
1141
- return;
1142
- }
1143
- containerObserver = new MutationObserver(syncIframe);
1144
- containerObserver.observe(container, {
1145
- childList: true,
1146
- subtree: true
1147
- });
1148
- syncIframe();
1149
- };
1150
- (0, vue.watch)(() => resolveSandboxContainer(sandboxRef.value), setContainer, {
1151
- flush: "post",
1152
- immediate: true
1153
- });
1154
- (0, vue.onBeforeUnmount)(() => {
1155
- setContainer(null);
1156
- cancelMeasure();
1157
- });
1158
- return height;
1159
- };
1160
- var resolvePreviewBoxHeight = (contentHeight, verticalInset = 0, borderWidth = 1) => {
1161
- const content = Number.isFinite(contentHeight) ? Math.max(0, contentHeight) : 0;
1162
- const inset = Number.isFinite(verticalInset) ? Math.max(0, verticalInset) : 0;
1163
- const border = Number.isFinite(borderWidth) ? Math.max(0, borderWidth) : 1;
1164
- return content + inset * 2 + border * 2;
1165
- };
1166
- var CLIPPING_OVERFLOW = /* @__PURE__ */ new Set([
1167
- "auto",
1168
- "scroll",
1169
- "hidden",
1170
- "overlay"
1171
- ]);
1172
- var SCROLLABLE_OVERFLOW = /* @__PURE__ */ new Set([
1173
- "auto",
1174
- "scroll",
1175
- "overlay"
1176
- ]);
1177
- var getWindowInnerHeight = () => typeof window === "undefined" ? 0 : window.innerHeight;
1178
- var getPlaygroundHost = (el) => el?.closest(".docs-playground") ?? el;
1179
- var isPlaygroundExpandable = (value) => {
1180
- if (value === true) return true;
1181
- return typeof value === "number" && Number.isFinite(value) && value > 0;
1182
- };
1183
- var resolveRemainingPreviewHeight = ({ viewportHeight, chromeHeight, gap = 16 }) => Math.max(0, Math.round((Number.isFinite(viewportHeight) ? viewportHeight : 0) - (Number.isFinite(chromeHeight) ? Math.max(0, chromeHeight) : 0) - (Number.isFinite(gap) ? gap : 16)));
1184
- var resolveExpandedPreviewHeight = (expandable, availableHeight) => {
1185
- if (expandable === true) return Math.max(24, Math.round(Number.isFinite(availableHeight) ? availableHeight : 0));
1186
- return Math.max(24, Math.round(expandable));
1187
- };
1188
- var getVisibleViewportRect = (el) => {
1189
- const viewportHeight = getWindowInnerHeight();
1190
- let top = 0;
1191
- let bottom = viewportHeight;
1192
- if (typeof window === "undefined" || !el) return {
1193
- top,
1194
- bottom,
1195
- height: Math.max(0, Math.round(bottom - top))
1196
- };
1197
- let node = getPlaygroundHost(el)?.parentElement ?? null;
1198
- while (node && node !== document.documentElement) {
1199
- const overflowY = getComputedStyle(node).overflowY;
1200
- if (CLIPPING_OVERFLOW.has(overflowY)) {
1201
- const rect = node.getBoundingClientRect();
1202
- if (Number.isFinite(rect.top)) top = Math.max(top, rect.top);
1203
- if (Number.isFinite(rect.bottom)) bottom = Math.min(bottom, rect.bottom);
1204
- }
1205
- node = node.parentElement;
1206
- }
1207
- return {
1208
- top,
1209
- bottom,
1210
- height: Math.max(0, Math.round(bottom - top))
1211
- };
1212
- };
1213
- var findScrollableAncestor = (el) => {
1214
- if (!el || typeof window === "undefined") return null;
1215
- let node = getPlaygroundHost(el)?.parentElement ?? null;
1216
- while (node && node !== document.documentElement) {
1217
- const overflowY = getComputedStyle(node).overflowY;
1218
- if (SCROLLABLE_OVERFLOW.has(overflowY) && node.scrollHeight > node.clientHeight + 1) return node;
1219
- node = node.parentElement;
1220
- }
1221
- return null;
1222
- };
1223
- var scrollPlaygroundToViewportStart = (el) => {
1224
- if (!el) return;
1225
- const host = getPlaygroundHost(el) ?? el;
1226
- const scroller = findScrollableAncestor(host);
1227
- if (scroller) {
1228
- const delta = host.getBoundingClientRect().top - scroller.getBoundingClientRect().top;
1229
- scroller.scrollTop = Math.max(0, scroller.scrollTop + delta);
1230
- return;
1231
- }
1232
- if (typeof host.scrollIntoView === "function") host.scrollIntoView({
1233
- block: "start",
1234
- behavior: "auto"
1235
- });
1236
- };
1237
- //#endregion
1238
- //#region packages/playground/src/core/runtime/error-guard.ts
1239
- var UNKNOWN_RUNTIME_ERROR = "运行时发生未知错误";
1240
- var MODULE_SPECIFIER_RE = /Failed to resolve module specifier|Error resolving module specifier/u;
1241
- var forwardSandboxRuntimeError = (event) => {
1242
- if (!(event instanceof ErrorEvent) || event.error != null || !event.message) return;
1243
- event.stopImmediatePropagation();
1244
- if (event.message === "ResizeObserver loop limit exceeded" || event.message === "ResizeObserver loop completed with undelivered notifications.") return;
1245
- window.parent.postMessage({
1246
- action: "error",
1247
- value: event.message
1248
- }, "*");
1249
- };
1250
- var SANDBOX_RUNTIME_ERROR_CAPTURE_HTML = [
1251
- "<script>",
1252
- `window.addEventListener('error', ${forwardSandboxRuntimeError.toString()}, true);`,
1253
- "<\/script>"
1254
- ].join("\n");
1255
- var toRuntimeErrorMessage = (value, message) => {
1256
- if (typeof value === "string" && value) return value;
1257
- if (value instanceof Error && value.message) return value.message;
1258
- if (value && typeof value === "object" && "message" in value) {
1259
- const valueMessage = value.message;
1260
- if (typeof valueMessage === "string" && valueMessage) return valueMessage;
1261
- }
1262
- if (typeof message === "string" && message) return message;
1263
- if (value !== null && value !== void 0) try {
1264
- return String(value);
1265
- } catch {
1266
- return UNKNOWN_RUNTIME_ERROR;
1267
- }
1268
- return UNKNOWN_RUNTIME_ERROR;
1269
- };
1270
- var normalizeRuntimeErrorMessage = (data) => {
1271
- if (!data || typeof data !== "object") return false;
1272
- const message = data;
1273
- if (message.action !== "error") return false;
1274
- message.value = toRuntimeErrorMessage(message.value, message.message);
1275
- return true;
1276
- };
1277
- var toErrorText = (value) => {
1278
- if (typeof value === "string" && value) return value;
1279
- if (value instanceof Error && value.message) return value.message;
1280
- return "";
1281
- };
1282
- var formatSandboxRuntimeError = (message, importMapTip) => {
1283
- if (!MODULE_SPECIFIER_RE.test(message)) return message;
1284
- return `${message.replace(/\. Relative references must.*$/u, "").replace(/\.$/u, "")}.\n${importMapTip}`;
1285
- };
1286
- var useSandboxRuntimeErrorGuard = (sandboxRef) => {
1287
- const error = (0, vue.ref)("");
1288
- if (typeof window === "undefined") return error;
1289
- const handleMessage = (event) => {
1290
- const source = resolveSandboxMessageSource(sandboxRef.value);
1291
- if (!source || event.source !== source) return;
1292
- if (!normalizeRuntimeErrorMessage(event.data)) return;
1293
- error.value = event.data.value;
1294
- };
1295
- window.addEventListener("message", handleMessage, true);
1296
- (0, vue.onBeforeUnmount)(() => window.removeEventListener("message", handleMessage, true));
1297
- return error;
1298
- };
1299
- //#endregion
1300
- //#region packages/playground/src/core/runtime/viewport.ts
1301
- var DEFAULT_VIEWPORT_OPTIONS = ["auto", 375];
1302
- var isValidViewport = (viewport) => {
1303
- if (viewport === "auto") return true;
1304
- if (typeof viewport === "number") return Number.isFinite(viewport) && viewport > 0;
1305
- return Array.isArray(viewport) && viewport.length === 2 && viewport.every((value) => typeof value === "number" && Number.isFinite(value) && value > 0);
1306
- };
1307
- var getViewportKey = (viewport) => Array.isArray(viewport) ? `${viewport[0]}x${viewport[1]}` : String(viewport);
1308
- var viewportEquals = (left, right) => getViewportKey(left) === getViewportKey(right);
1309
- var cloneViewport = (viewport) => Array.isArray(viewport) ? [viewport[0], viewport[1]] : viewport;
1310
- var normalizeViewportOptions = (options) => {
1311
- const source = options === void 0 ? DEFAULT_VIEWPORT_OPTIONS : options;
1312
- const normalized = [];
1313
- for (const viewport of source) {
1314
- if (!isValidViewport(viewport)) continue;
1315
- if (normalized.some((item) => viewportEquals(item, viewport))) continue;
1316
- normalized.push(cloneViewport(viewport));
1317
- }
1318
- return normalized;
1319
- };
1320
- var resolveInitialViewport = (viewport, options) => isValidViewport(viewport) ? cloneViewport(viewport) : cloneViewport(options[0] || "auto");
1321
- var includeActiveViewport = (options, viewport) => {
1322
- if (!options.length || options.some((item) => viewportEquals(item, viewport))) return options;
1323
- return [...options, cloneViewport(viewport)];
1324
- };
1325
- var getViewportWidth = (viewport) => {
1326
- if (viewport === "auto") return null;
1327
- return Array.isArray(viewport) ? viewport[0] : viewport;
1328
- };
1329
- var getViewportHeight = (viewport) => Array.isArray(viewport) ? viewport[1] : null;
1330
- var formatViewportLabel = (viewport, autoLabel = "Auto") => {
1331
- if (viewport === "auto") return autoLabel;
1332
- return Array.isArray(viewport) ? `${viewport[0]} × ${viewport[1]}px` : `${viewport}px`;
1333
- };
1334
- var resolvePopupScreenInset = (gap = 40) => {
1335
- const safeGap = Number.isFinite(gap) ? Math.max(0, gap) : 40;
1336
- return {
1337
- x: safeGap * 2,
1338
- y: safeGap
1339
- };
1340
- };
1341
- var getWindowInnerSize = () => {
1342
- if (typeof window === "undefined") return {
1343
- width: 0,
1344
- height: 0
1345
- };
1346
- return {
1347
- width: window.innerWidth,
1348
- height: window.innerHeight
1349
- };
1350
- };
1351
- var resolvePopupRequestedSize = (viewport, screen = getWindowInnerSize(), gap = 40) => {
1352
- const inset = resolvePopupScreenInset(gap);
1353
- const maxWidth = Math.max(0, Math.round((Number.isFinite(screen.width) ? screen.width : 0) - inset.x));
1354
- const maxHeight = Math.max(0, Math.round((Number.isFinite(screen.height) ? screen.height : 0) - inset.y));
1355
- const viewportWidth = getViewportWidth(viewport);
1356
- const viewportHeight = getViewportHeight(viewport);
1357
- return {
1358
- width: viewportWidth ?? maxWidth,
1359
- height: viewportHeight ?? maxHeight,
1360
- maxWidth,
1361
- maxHeight
1362
- };
1363
- };
1364
- /**
1365
- * 壳层封顶到 `screen.width - gap * 2`、`screen.height - gap`。
1366
- * 未设 viewport 边时,画布铺满工具栏以下区域;请求尺寸大于可用区域时由 Scroller 处理溢出。
1367
- * @param viewport 当前 playground 视口配置。
1368
- * @param screen 屏幕可用宽高,默认取 `window.inner*`。
1369
- * @param gap 相对屏幕上下边缘的留白;左右为该值的 2 倍。
1370
- * @returns 壳层、内容区与画布尺寸,以及是否需要滚动。
1371
- */
1372
- var resolvePopupLayout = (viewport, screen = getWindowInnerSize(), gap = 40) => {
1373
- const requested = resolvePopupRequestedSize(viewport, screen, gap);
1374
- const shellWidth = Math.min(requested.width, requested.maxWidth);
1375
- const shellHeight = Math.min(requested.height, requested.maxHeight);
1376
- const bodyHeight = Math.max(0, shellHeight - 44);
1377
- const canvasWidth = getViewportWidth(viewport) ?? shellWidth;
1378
- const canvasHeight = getViewportHeight(viewport) ?? bodyHeight;
1379
- return {
1380
- shellWidth,
1381
- shellHeight,
1382
- bodyHeight,
1383
- canvasWidth,
1384
- canvasHeight,
1385
- needsScrollX: canvasWidth > shellWidth,
1386
- needsScrollY: canvasHeight > bodyHeight
1387
- };
1388
- };
1389
- //#endregion
1390
- //#region packages/playground/src/core/runtime/theme.ts
1391
- var resolveElementTheme = (element) => {
1392
- const vcTheme = element?.getAttribute("data-vc-theme");
1393
- if (vcTheme === "light" || vcTheme === "dark") return vcTheme;
1394
- const docsTheme = element?.getAttribute("data-doc-theme");
1395
- return docsTheme === "light" || docsTheme === "dark" ? docsTheme : null;
1396
- };
1397
- var resolveHostTheme = (container) => {
1398
- let current = container;
1399
- while (current) {
1400
- const theme = resolveElementTheme(current);
1401
- if (theme) return theme;
1402
- current = current.parentElement;
1403
- }
1404
- return typeof document === "undefined" ? null : resolveElementTheme(document.body) || resolveElementTheme(document.documentElement);
1405
- };
1406
- var syncSandboxTheme = (iframe, theme) => {
1407
- if (!iframe) return;
1408
- try {
1409
- const iframeDocument = iframe.contentDocument;
1410
- if (!iframeDocument?.body) return;
1411
- if (!theme) {
1412
- iframeDocument.body.removeAttribute("data-doc-theme");
1413
- iframeDocument.body.removeAttribute("data-vc-theme");
1414
- iframeDocument.documentElement.removeAttribute("data-doc-theme");
1415
- iframeDocument.documentElement.removeAttribute("data-vc-theme");
1416
- iframeDocument.documentElement.style.removeProperty("color-scheme");
1417
- return;
1418
- }
1419
- iframeDocument.body.setAttribute("data-doc-theme", theme);
1420
- iframeDocument.body.setAttribute("data-vc-theme", theme);
1421
- iframeDocument.documentElement.setAttribute("data-doc-theme", theme);
1422
- iframeDocument.documentElement.setAttribute("data-vc-theme", theme);
1423
- iframeDocument.documentElement.style.colorScheme = theme;
1424
- } catch {}
1425
- };
1426
- var useSandboxTheme = (sandboxRef) => {
1427
- let container = null;
1428
- let iframe = null;
1429
- let containerObserver = null;
1430
- let themeObserver = null;
1431
- const syncTheme = () => syncSandboxTheme(iframe, resolveHostTheme(container));
1432
- const handleIframeLoad = () => syncTheme();
1433
- const setIframe = (nextIframe) => {
1434
- if (iframe === nextIframe) {
1435
- syncTheme();
1436
- return;
1437
- }
1438
- iframe?.removeEventListener("load", handleIframeLoad);
1439
- iframe = nextIframe;
1440
- iframe?.addEventListener("load", handleIframeLoad);
1441
- syncTheme();
1442
- };
1443
- const syncIframe = () => setIframe(container?.querySelector("iframe") || null);
1444
- const setContainer = (nextContainer) => {
1445
- if (container === nextContainer) {
1446
- syncIframe();
1447
- return;
1448
- }
1449
- containerObserver?.disconnect();
1450
- containerObserver = null;
1451
- container = nextContainer;
1452
- setIframe(null);
1453
- if (!container) return;
1454
- if (typeof MutationObserver !== "undefined") {
1455
- containerObserver = new MutationObserver(syncIframe);
1456
- containerObserver.observe(container, {
1457
- childList: true,
1458
- subtree: true
1459
- });
1460
- }
1461
- syncIframe();
1462
- };
1463
- (0, vue.watch)(() => resolveSandboxContainer(sandboxRef.value), setContainer, {
1464
- flush: "post",
1465
- immediate: true
1466
- });
1467
- if (typeof document !== "undefined" && typeof MutationObserver !== "undefined") {
1468
- themeObserver = new MutationObserver(syncTheme);
1469
- themeObserver.observe(document.documentElement, {
1470
- attributes: true,
1471
- attributeFilter: ["data-doc-theme", "data-vc-theme"],
1472
- subtree: true
1473
- });
1474
- }
1475
- (0, vue.onBeforeUnmount)(() => {
1476
- themeObserver?.disconnect();
1477
- themeObserver = null;
1478
- setContainer(null);
1479
- });
1480
- };
1481
- //#endregion
1482
- //#region packages/playground/src/core/runtime/height-bridge.ts
1483
- /**
1484
- * 只接收本宿主当前 iframe 的高度请求,应用重评时切换会话。
1485
- * @param sandbox 预览容器。
1486
- * @param maxHeight 当前可用高度,普通预览默认不限制。
1487
- * @returns 所有有效请求的最大高度。
1488
- */
1489
- function usePreviewHeightBridge(sandbox, maxHeight = () => Infinity) {
1490
- const height = (0, vue.ref)(0);
1491
- const requests = /* @__PURE__ */ new Map();
1492
- let source = null;
1493
- let session = "";
1494
- let disposed = false;
1495
- const sync = () => {
1496
- height.value = Math.max(0, ...requests.values());
1497
- };
1498
- const reset = () => {
1499
- requests.clear();
1500
- session = "";
1501
- source = null;
1502
- sync();
1503
- };
1504
- const handleMessage = async (event) => {
1505
- const current = resolveSandboxMessageSource(sandbox.value);
1506
- const data = event.data;
1507
- if (!current || event.source !== current || data?.action !== "docs:height" || typeof data.session !== "string") return;
1508
- if (data.operation === "start") {
1509
- reset();
1510
- source = current;
1511
- session = data.session;
1512
- return;
1513
- }
1514
- if (source !== current || session !== data.session) return;
1515
- if (data.operation === "stop") {
1516
- reset();
1517
- return;
1518
- }
1519
- if (!Number.isSafeInteger(data.id) || data.id <= 0) return;
1520
- if (data.operation === "release") {
1521
- requests.delete(data.id);
1522
- sync();
1523
- return;
1524
- }
1525
- if (data.operation !== "enable" || !Number.isFinite(data.height) || data.height <= 0) return;
1526
- const accepted = data.height <= maxHeight();
1527
- if (accepted) requests.set(data.id, data.height);
1528
- sync();
1529
- const receiver = source;
1530
- await (0, vue.nextTick)();
1531
- if (disposed || source !== receiver || session !== data.session) return;
1532
- receiver?.postMessage({
1533
- action: "docs:height:applied",
1534
- session,
1535
- id: data.id,
1536
- accepted
1537
- }, "*");
1538
- };
1539
- (0, vue.watch)(() => resolveSandboxMessageSource(sandbox.value), reset, { flush: "sync" });
1540
- if (typeof window !== "undefined") window.addEventListener("message", handleMessage);
1541
- (0, vue.onBeforeUnmount)(() => {
1542
- disposed = true;
1543
- if (typeof window !== "undefined") window.removeEventListener("message", handleMessage);
1544
- reset();
1545
- });
1546
- return height;
1547
- }
1548
- //#endregion
1549
- //#region packages/playground/src/core/runtime/toolbar.vue?vue&type=script&setup=true&lang.ts
1550
- var _hoisted_1$4 = { class: "docs-playground__tools" };
1551
- var _hoisted_2$3 = {
1552
- key: 0,
1553
- class: "docs-playground__tool-group"
1554
- };
1555
- var _hoisted_3$3 = [
1556
- "title",
1557
- "aria-label",
1558
- "aria-expanded"
1559
- ];
1560
- var _hoisted_4$3 = {
1561
- key: 1,
1562
- class: "docs-playground__tool-divider",
1563
- "aria-hidden": "true"
1564
- };
1565
- var _hoisted_5$3 = ["title", "aria-label"];
1566
- var _hoisted_6$2 = ["title", "aria-label"];
1567
- var _hoisted_7$2 = ["title", "aria-label"];
1568
- var _hoisted_8$1 = {
1569
- key: 3,
1570
- class: "docs-playground__tool-divider",
1571
- "aria-hidden": "true"
1572
- };
1573
- var _hoisted_9 = ["aria-label"];
1574
- var _hoisted_10 = [
1575
- "title",
1576
- "aria-label",
1577
- "aria-selected",
1578
- "aria-pressed",
1579
- "onClick"
1580
- ];
1581
- var _hoisted_11 = {
1582
- key: 2,
1583
- class: "docs-playground__tool-divider",
1584
- "aria-hidden": "true"
1585
- };
1586
- var _hoisted_12 = ["title", "aria-label"];
1587
- //#endregion
1588
- //#region packages/playground/src/core/runtime/toolbar.vue
1589
- var toolbar_default = /* @__PURE__ */ (0, vue.defineComponent)({
1590
- __name: "toolbar",
1591
- props: {
1592
- copyValue: { default: "" },
1593
- viewport: { default: "auto" },
1594
- viewportOptions: { default: () => ["auto", 375] },
1595
- views: { default: () => [] },
1596
- activeView: { default: "runtime" },
1597
- showRuntimeActions: {
1598
- type: Boolean,
1599
- default: true
1600
- },
1601
- showOpenPopup: {
1602
- type: Boolean,
1603
- default: false
1604
- },
1605
- showClose: {
1606
- type: Boolean,
1607
- default: false
1608
- }
1609
- },
1610
- emits: [
1611
- "refresh",
1612
- "edit",
1613
- "open-popup",
1614
- "close-popup",
1615
- "viewport-change",
1616
- "view-change"
1617
- ],
1618
- setup(__props, { emit: __emit }) {
1619
- const props = __props;
1620
- const emit = __emit;
1621
- const { t } = (0, _deot_docs_locale.useLocale)();
1622
- const VIEW_ORDER = ["runtime", "files"];
1623
- const viewportMenuVisible = (0, vue.ref)(false);
1624
- const orderedViews = (0, vue.computed)(() => VIEW_ORDER.filter((view) => props.views.includes(view)));
1625
- const showViews = (0, vue.computed)(() => orderedViews.value.length > 1);
1626
- const viewportLabel = (0, vue.computed)(() => formatViewportLabel(props.viewport, t("playground.runtime.auto")));
1627
- const getViewAriaLabel = (view) => t(playgroundViewMessage(view));
1628
- const getViewLabel = (view) => view === "runtime" ? t("playground.runtime.previewLabel") : t("playground.runtime.codeLabel");
1629
- const handleViewport = (index) => {
1630
- const viewport = props.viewportOptions[index];
1631
- if (viewport && !viewportEquals(viewport, props.viewport)) emit("viewport-change", viewport);
1632
- };
1633
- (0, vue.watch)(() => props.viewportOptions.length, (length) => {
1634
- if (length <= 1) viewportMenuVisible.value = false;
1635
- });
1636
- (0, vue.watch)(() => props.showRuntimeActions, (enabled) => {
1637
- if (!enabled) viewportMenuVisible.value = false;
1638
- });
1639
- return (_ctx, _cache) => {
1640
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$4, [
1641
- __props.showRuntimeActions ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [
1642
- __props.viewportOptions.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$3, [(0, vue.createVNode)((0, vue.unref)(_deot_vc.Dropdown), {
1643
- modelValue: viewportMenuVisible.value,
1644
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => viewportMenuVisible.value = $event),
1645
- class: "docs-playground__viewport-menu",
1646
- portal: true,
1647
- trigger: "click",
1648
- placement: "bottom-right"
1649
- }, {
1650
- content: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_deot_vc.DropdownMenu), {
1651
- class: "docs-playground__viewport-options",
1652
- role: "menu",
1653
- "aria-label": (0, vue.unref)(t)("playground.runtime.viewportMenu")
1654
- }, {
1655
- default: (0, vue.withCtx)(() => [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(__props.viewportOptions, (item, index) => {
1656
- return (0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_deot_vc.DropdownItem), {
1657
- key: (0, vue.unref)(getViewportKey)(item),
1658
- class: "docs-playground__viewport-option",
1659
- value: index,
1660
- selected: (0, vue.unref)(viewportEquals)(item, __props.viewport),
1661
- role: "menuitemradio",
1662
- "aria-checked": (0, vue.unref)(viewportEquals)(item, __props.viewport),
1663
- onClick: ($event) => handleViewport(index)
1664
- }, {
1665
- default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)((0, vue.unref)(formatViewportLabel)(item, (0, vue.unref)(t)("playground.runtime.auto"))), 1)]),
1666
- _: 2
1667
- }, 1032, [
1668
- "value",
1669
- "selected",
1670
- "aria-checked",
1671
- "onClick"
1672
- ]);
1673
- }), 128))]),
1674
- _: 1
1675
- }, 8, ["aria-label"])]),
1676
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("button", {
1677
- type: "button",
1678
- class: (0, vue.normalizeClass)(["docs-playground__tool docs-playground__viewport-trigger", { "is-active": __props.viewport !== "auto" }]),
1679
- title: (0, vue.unref)(t)("playground.runtime.viewport", { value: viewportLabel.value }),
1680
- "aria-label": (0, vue.unref)(t)("playground.runtime.viewport", { value: viewportLabel.value }),
1681
- "aria-expanded": viewportMenuVisible.value,
1682
- "aria-haspopup": "menu"
1683
- }, [(0, vue.createVNode)((0, vue.unref)(icon_default), { name: "viewport" })], 10, _hoisted_3$3)]),
1684
- _: 1
1685
- }, 8, ["modelValue"])])) : (0, vue.createCommentVNode)("v-if", true),
1686
- __props.viewportOptions.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4$3)) : (0, vue.createCommentVNode)("v-if", true),
1687
- (0, vue.createElementVNode)("button", {
1688
- type: "button",
1689
- class: "docs-playground__tool docs-playground__refresh",
1690
- "data-action": "refresh",
1691
- title: (0, vue.unref)(t)("playground.runtime.refresh"),
1692
- "aria-label": (0, vue.unref)(t)("playground.runtime.refresh"),
1693
- onClick: _cache[1] || (_cache[1] = ($event) => emit("refresh"))
1694
- }, [(0, vue.createVNode)((0, vue.unref)(icon_default), { name: "refresh" })], 8, _hoisted_5$3),
1695
- (0, vue.createElementVNode)("button", {
1696
- type: "button",
1697
- class: "docs-playground__tool docs-playground__editor",
1698
- "data-action": "edit",
1699
- title: (0, vue.unref)(t)("playground.runtime.editFiles"),
1700
- "aria-label": (0, vue.unref)(t)("playground.runtime.editFiles"),
1701
- onClick: _cache[2] || (_cache[2] = ($event) => emit("edit"))
1702
- }, [(0, vue.createVNode)((0, vue.unref)(icon_default), { name: "editor" })], 8, _hoisted_6$2),
1703
- __props.showOpenPopup ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
1704
- key: 2,
1705
- type: "button",
1706
- class: "docs-playground__tool docs-playground__popup",
1707
- "data-action": "open-popup",
1708
- title: (0, vue.unref)(t)("playground.runtime.openPopup"),
1709
- "aria-label": (0, vue.unref)(t)("playground.runtime.openPopup"),
1710
- onClick: _cache[3] || (_cache[3] = ($event) => emit("open-popup"))
1711
- }, [(0, vue.createVNode)((0, vue.unref)(icon_default), { name: "popup" })], 8, _hoisted_7$2)) : (0, vue.createCommentVNode)("v-if", true),
1712
- (0, vue.createVNode)((0, vue.unref)(_deot_vc.Clipboard), {
1713
- class: "docs-playground__tool",
1714
- value: __props.copyValue,
1715
- tag: "button",
1716
- type: "button",
1717
- title: (0, vue.unref)(t)("playground.common.copy"),
1718
- "aria-label": (0, vue.unref)(t)("playground.common.copy")
1719
- }, {
1720
- default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(icon_default), { name: "copy" })]),
1721
- _: 1
1722
- }, 8, [
1723
- "value",
1724
- "title",
1725
- "aria-label"
1726
- ]),
1727
- showViews.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_8$1)) : (0, vue.createCommentVNode)("v-if", true)
1728
- ], 64)) : (0, vue.createCommentVNode)("v-if", true),
1729
- showViews.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
1730
- key: 1,
1731
- class: "docs-playground__views",
1732
- role: "tablist",
1733
- "aria-label": (0, vue.unref)(t)("playground.runtime.views")
1734
- }, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(orderedViews.value, (item) => {
1735
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
1736
- key: item,
1737
- type: "button",
1738
- class: (0, vue.normalizeClass)(["docs-playground__view", { "is-active": item === __props.activeView }]),
1739
- role: "tab",
1740
- title: getViewAriaLabel(item),
1741
- "aria-label": getViewAriaLabel(item),
1742
- "aria-selected": item === __props.activeView,
1743
- "aria-pressed": item === __props.activeView,
1744
- onClick: ($event) => emit("view-change", item)
1745
- }, (0, vue.toDisplayString)(getViewLabel(item)), 11, _hoisted_10);
1746
- }), 128))], 8, _hoisted_9)) : (0, vue.createCommentVNode)("v-if", true),
1747
- __props.showClose && (__props.showRuntimeActions || showViews.value) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_11)) : (0, vue.createCommentVNode)("v-if", true),
1748
- __props.showClose ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
1749
- key: 3,
1750
- type: "button",
1751
- class: "docs-playground__tool docs-playground__popup-close",
1752
- "data-action": "close-popup",
1753
- title: (0, vue.unref)(t)("playground.runtime.closePopup"),
1754
- "aria-label": (0, vue.unref)(t)("playground.runtime.closePopup"),
1755
- onClick: _cache[4] || (_cache[4] = ($event) => emit("close-popup"))
1756
- }, [..._cache[5] || (_cache[5] = [(0, vue.createElementVNode)("span", {
1757
- class: "docs-playground__popup-close-mark",
1758
- "aria-hidden": "true"
1759
- }, "✕", -1)])], 8, _hoisted_12)) : (0, vue.createCommentVNode)("v-if", true)
1760
- ]);
1761
- };
1762
- }
1763
- });
1764
- //#endregion
1765
- //#region packages/playground/src/import-map.ts
1766
- /**
1767
- * 站点级同步内存:`$docs.modules` / `$docs.styles` 与管理页 import、CSS 覆盖。
1768
- */
1769
- var normalize = (value) => value.trim();
1770
- var isSafePlaygroundHref = (href) => {
1771
- const next = href.trim();
1772
- if (!next) return false;
1773
- if (/^https?:\/\//iu.test(next)) return true;
1774
- return next.startsWith("/") && !next.startsWith("//");
1775
- };
1776
- var filterSafeHrefs = (value = {}) => Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string" && isSafePlaygroundHref(entry[1])));
1777
- var sanitizeMap = (value = {}) => filterSafeHrefs(Object.fromEntries(Object.entries(value).map(([alias, url]) => [normalize(alias), normalize(url)]).filter(([alias]) => Boolean(alias))));
1778
- var createMapStore = () => {
1779
- let data = {};
1780
- const snapshot = () => ({ ...data });
1781
- const drop = (alias) => {
1782
- if (data[alias] === void 0) return snapshot();
1783
- const next = { ...data };
1784
- delete next[alias];
1785
- data = next;
1786
- return snapshot();
1787
- };
1788
- return {
1789
- get: snapshot,
1790
- set: (value = {}) => {
1791
- data = sanitizeMap(value);
1792
- },
1793
- apply: (alias, url) => {
1794
- const nextAlias = normalize(alias);
1795
- const nextUrl = normalize(url);
1796
- if (!nextAlias) return snapshot();
1797
- if (!isSafePlaygroundHref(nextUrl)) return drop(nextAlias);
1798
- data = {
1799
- ...data,
1800
- [nextAlias]: nextUrl
1801
- };
1802
- return snapshot();
1803
- },
1804
- remove: (alias) => {
1805
- const nextAlias = normalize(alias);
1806
- return nextAlias ? drop(nextAlias) : snapshot();
1807
- }
1808
- };
1809
- };
1810
- var siteModules = createMapStore();
1811
- var siteStyles = createMapStore();
1812
- var importOverrides = createMapStore();
1813
- var styleOverrides = createMapStore();
1814
- /**
1815
- * 站点默认 import。合并时夹在内置 CDN 与实例 `builtinImportMap` 之间。
1816
- */
1817
- var getPlaygroundSiteModules = siteModules.get;
1818
- var setPlaygroundSiteModules = siteModules.set;
1819
- /**
1820
- * 站点默认预览 CSS。合并时夹在内置样式表与管理页覆盖之间。
1821
- */
1822
- var getPlaygroundSiteStyles = siteStyles.get;
1823
- var setPlaygroundSiteStyles = siteStyles.set;
1824
- var getPlaygroundImportMapOverrides = importOverrides.get;
1825
- var setPlaygroundImportMapOverrides = importOverrides.set;
1826
- var applyPlaygroundImportMapOverride = importOverrides.apply;
1827
- var removePlaygroundImportMapOverride = importOverrides.remove;
1828
- var getPlaygroundStyleOverrides = styleOverrides.get;
1829
- var setPlaygroundStyleOverrides = styleOverrides.set;
1830
- var applyPlaygroundStyleOverride = styleOverrides.apply;
1831
- var removePlaygroundStyleOverride = styleOverrides.remove;
1832
- var clearPlaygroundMaps = () => {
1833
- [
1834
- siteModules,
1835
- siteStyles,
1836
- importOverrides,
1837
- styleOverrides
1838
- ].forEach((store) => store.set());
1839
- };
1840
- //#endregion
1841
- //#region packages/playground/src/preview-service.ts
1842
- /** Preview iframe 内的高度服务;class 会被序列化到 iframe 执行。 */
1843
- var PlaygroundService = class {
1844
- target;
1845
- vue;
1846
- session;
1847
- /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1848
- sequence = 0;
1849
- /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1850
- disposed = false;
1851
- manual;
1852
- /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1853
- requests = /* @__PURE__ */ new Set();
1854
- /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1855
- owners = /* @__PURE__ */ new Set();
1856
- /**
1857
- * @param target 当前预览窗口。
1858
- * @param vue 与预览应用共用的 Vue 实例。
1859
- */
1860
- /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1861
- constructor(target, vue) {
1862
- this.target = target;
1863
- this.vue = vue;
1864
- this.session = `${Date.now()}-${Math.random()}`;
1865
- this.dispose = this.dispose.bind(this);
1866
- this.post("start");
1867
- this.target.addEventListener("pagehide", this.dispose);
1868
- }
1869
- /**
1870
- * 申请手动临时高度;新请求会替换上一个手动请求。
1871
- * @param height iframe 最小高度。
1872
- * @returns iframe 是否在超时前应用目标高度。
1873
- */
1874
- enable(height) {
1875
- this.validateHeight(height);
1876
- this.manual?.release();
1877
- this.manual = this.createRequest(height);
1878
- return this.manual.ready;
1879
- }
1880
- /** 释放手动临时高度,不影响 `run` 创建的请求。 */
1881
- disable() {
1882
- this.manual?.release();
1883
- this.manual = void 0;
1884
- }
1885
- /**
1886
- * 创建等待高度后执行的事件处理函数。
1887
- * @param height iframe 最小高度。
1888
- * @param optionsOrHandler 可见状态配置,或最后一个 handler。
1889
- * @param lastHandler 使用配置时的最后一个 handler。
1890
- * @returns 保留 handler 参数和结果的异步事件处理函数。
1891
- */
1892
- run(height, optionsOrHandler, lastHandler) {
1893
- this.validateHeight(height);
1894
- if (optionsOrHandler === void 0) return (visible) => {
1895
- if (visible) return this.enable(height);
1896
- this.disable();
1897
- return Promise.resolve(void 0);
1898
- };
1899
- const visible = (typeof optionsOrHandler === "function" ? {} : optionsOrHandler).visible;
1900
- if (visible !== void 0 && !this.vue.isRef(visible) && typeof visible !== "function") throw new TypeError("Playground visible must be a ref or getter");
1901
- const handler = typeof optionsOrHandler === "function" ? optionsOrHandler : lastHandler ?? (this.vue.isRef(visible) ? () => {
1902
- visible.value = true;
1903
- } : void 0);
1904
- if (typeof handler !== "function") throw new TypeError("Playground run requires a handler");
1905
- let ownerDisposed = false;
1906
- let current;
1907
- let pending;
1908
- const disposeOwner = () => {
1909
- ownerDisposed = true;
1910
- current?.release();
1911
- this.owners.delete(disposeOwner);
1912
- };
1913
- this.owners.add(disposeOwner);
1914
- if (this.vue.getCurrentScope()) this.vue.onScopeDispose(disposeOwner);
1915
- return (...args) => {
1916
- if (this.disposed || ownerDisposed) return Promise.resolve(void 0);
1917
- if (pending) return pending;
1918
- const request = current = this.createRequest(height);
1919
- let executionFinished = false;
1920
- const unlock = () => {
1921
- if (executionFinished && request.released && current === request) {
1922
- pending = void 0;
1923
- current = void 0;
1924
- }
1925
- };
1926
- request.closed.then(unlock);
1927
- const execution = (async () => {
1928
- try {
1929
- if (!await request.ready || ownerDisposed || this.disposed) return void 0;
1930
- if (visible) request.watch(visible);
1931
- const result = await handler(...args);
1932
- if (!visible || !this.vue.toValue(visible)) request.release();
1933
- return result;
1934
- } catch (error) {
1935
- request.release();
1936
- throw error;
1937
- } finally {
1938
- executionFinished = true;
1939
- unlock();
1940
- }
1941
- })();
1942
- pending = execution;
1943
- return execution;
1944
- };
1945
- }
1946
- /** 释放当前预览应用持有的全部高度和监听。 */
1947
- /* istanbul ignore next -- 注入测试会从序列化后的 iframe class 调用该方法。 */
1948
- dispose() {
1949
- if (this.disposed) return;
1950
- this.disposed = true;
1951
- for (const disposeOwner of this.owners) disposeOwner();
1952
- for (const request of this.requests) request.release();
1953
- this.post("stop");
1954
- this.target.removeEventListener("pagehide", this.dispose);
1955
- if (this.target.$docsPlayground === this) delete this.target.$docsPlayground;
1956
- }
1957
- /** @param height iframe 最小高度。 */
1958
- validateHeight(height) {
1959
- if (!Number.isFinite(height) || height <= 0) throw new TypeError("Playground height must be a positive finite number");
1960
- }
1961
- /**
1962
- * @param operation 请求操作。
1963
- * @param id 请求标识。
1964
- * @param height iframe 最小高度。
1965
- */
1966
- /* istanbul ignore next -- 构造和销毁时会从序列化后的 iframe class 调用该方法。 */
1967
- post(operation, id = 0, height = 0) {
1968
- this.target.parent.postMessage({
1969
- action: "docs:height",
1970
- session: this.session,
1971
- operation,
1972
- id,
1973
- height
1974
- }, "*");
1975
- }
1976
- /**
1977
- * @param height iframe 最小高度。
1978
- * @returns 内部等待与释放句柄。
1979
- */
1980
- createRequest(height) {
1981
- this.validateHeight(height);
1982
- const id = ++this.sequence;
1983
- let released = false;
1984
- let settled = false;
1985
- let frame = 0;
1986
- let timer = 0;
1987
- let stopWatch;
1988
- let resolveReady;
1989
- let resolveClosed;
1990
- const ready = new Promise((resolve) => {
1991
- resolveReady = resolve;
1992
- });
1993
- const closed = new Promise((resolve) => {
1994
- resolveClosed = resolve;
1995
- });
1996
- const stopWaiting = () => {
1997
- this.target.cancelAnimationFrame(frame);
1998
- if (timer) {
1999
- this.target.clearTimeout(timer);
2000
- timer = 0;
2001
- }
2002
- this.target.removeEventListener("message", onMessage);
2003
- };
2004
- const release = () => {
2005
- if (released) return;
2006
- released = true;
2007
- stopWaiting();
2008
- stopWatch?.();
2009
- resolveReady(false);
2010
- resolveClosed();
2011
- this.requests.delete(request);
2012
- this.post("release", id);
2013
- };
2014
- const check = () => {
2015
- if (released) return;
2016
- if (this.target.innerHeight >= height) {
2017
- settled = true;
2018
- stopWaiting();
2019
- resolveReady(true);
2020
- } else frame = this.target.requestAnimationFrame(check);
2021
- };
2022
- const onMessage = (event) => {
2023
- const data = event.data;
2024
- if (event.source !== this.target.parent || data?.action !== "docs:height:applied" || data.session !== this.session || data.id !== id || settled) return;
2025
- if (!data.accepted) release();
2026
- else check();
2027
- };
2028
- const request = {
2029
- ready,
2030
- closed,
2031
- get released() {
2032
- return released;
2033
- },
2034
- release,
2035
- watch: (visible) => {
2036
- stopWatch = this.vue.watch(() => this.vue.toValue(visible), (value) => {
2037
- if (!value) release();
2038
- }, { flush: "sync" });
2039
- }
2040
- };
2041
- this.requests.add(request);
2042
- if (this.disposed) release();
2043
- else {
2044
- this.target.addEventListener("message", onMessage);
2045
- timer = this.target.setTimeout(() => {
2046
- timer = 0;
2047
- release();
2048
- }, 2e3);
2049
- this.post("enable", id, height);
2050
- }
2051
- return request;
2052
- }
2053
- };
2054
- var PREVIEW_SERVICE_IMPORT_CODE = "import * as __docsPreviewVue from \"vue\"";
2055
- var PREVIEW_SERVICE_USE_CODE = [
2056
- `;const __docsPlayground=new (${PlaygroundService.toString()})(window,__docsPreviewVue);`,
2057
- "app.provide(\"docs:playground\",__docsPlayground);",
2058
- "window.$docsPlayground=__docsPlayground;",
2059
- "app.mixin({beforeUnmount(){if(this===this.$root)__docsPlayground.dispose()}});"
2060
- ].join("");
2061
- //#endregion
2062
- //#region packages/playground/src/core/repl-modules.ts
2063
- var { babelParse, MagicString, walkIdentifiers } = _vue_compiler_sfc_dist_compiler_sfc_esm_browser_js;
2064
- var moduleSpecifier = (item) => item.type === "import-meta" || !item.specifier || item.type === "dynamic" && item.glob ? void 0 : item.specifier;
2065
- /**
2066
- * 发现自定义 ESM 的传递引用;缺失的 npm 包映射到 CDN,动态依赖只登记地址。
2067
- * @param imports 当前预览的模块覆盖。
2068
- * @param specifiers 源文件实际引用的模块。
2069
- * @param resolve 本次预览共用的最终 URL 转换。
2070
- * @returns 补齐后的 import map。
2071
- */
2072
- var resolveReplImports = async (imports, specifiers, resolve = require_css.createCdnResolver()) => {
2073
- const resolved = { ...imports };
2074
- const visited = /* @__PURE__ */ new Set();
2075
- const visit = async (specifier, importer) => {
2076
- const source = require_css.isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier) ? new URL(specifier, importer || location.href).href : resolved[specifier] ||= require_css.resolveNpmImport(specifier);
2077
- const url = resolve(source);
2078
- if (visited.has(url) || require_css.isCdnEsmURL(source)) return;
2079
- visited.add(url);
2080
- const response = await fetch(url);
2081
- if (!response.ok) throw new Error(`${response.status} ${url}`);
2082
- const modules = (0, es_module_lexer.parse)(await response.text())[0];
2083
- await Promise.all(modules.flatMap((item) => {
2084
- const dependency = moduleSpecifier(item);
2085
- return dependency ? [visit(dependency, url)] : [];
2086
- }));
2087
- };
2088
- await Promise.all(specifiers.map((specifier) => visit(specifier)));
2089
- return resolved;
2090
- };
2091
- var rewriteImportedExports = (code) => {
2092
- const statements = babelParse(code, { sourceType: "module" }).program.body;
2093
- const bindings = /* @__PURE__ */ new Map();
2094
- const source = new MagicString(code);
2095
- for (const statement of statements) {
2096
- if (statement.type !== "ImportDeclaration") continue;
2097
- for (const spec of statement.specifiers) {
2098
- if (spec.type === "ImportNamespaceSpecifier") continue;
2099
- const imported = spec.type === "ImportDefaultSpecifier" ? "default" : spec.imported.type === "Identifier" ? spec.imported.name : JSON.stringify(spec.imported.value);
2100
- bindings.set(spec.local.name, {
2101
- imported,
2102
- source: statement.source.value,
2103
- rename: (name) => source.overwrite(spec.local.start, spec.local.end, spec.type === "ImportSpecifier" && spec.imported.start === spec.local.start ? `${imported} as ${name}` : name)
2104
- });
2105
- }
2106
- }
2107
- const aliases = /* @__PURE__ */ new Map();
2108
- const rewritten = /* @__PURE__ */ new Set();
2109
- for (const statement of statements) {
2110
- if (statement.type !== "ExportNamedDeclaration" || statement.source || statement.declaration) continue;
2111
- const exports = statement.specifiers.map((spec) => {
2112
- if (spec.type !== "ExportSpecifier") return "";
2113
- const local = spec.local.name;
2114
- const exported = spec.exported.type === "Identifier" ? spec.exported.name : JSON.stringify(spec.exported.value);
2115
- const binding = bindings.get(local);
2116
- if (!binding) return `export { ${local} as ${exported} };`;
2117
- if (!aliases.has(local)) {
2118
- const alias = `__docs_reexport_${aliases.size}__`;
2119
- aliases.set(local, alias);
2120
- binding.rename(alias);
2121
- }
2122
- return `export { ${binding.imported} as ${exported} } from ${JSON.stringify(binding.source)};`;
2123
- });
2124
- if (statement.specifiers.some((spec) => spec.type === "ExportSpecifier" && bindings.has(spec.local.name))) {
2125
- source.overwrite(statement.start, statement.end, exports.join("\n"));
2126
- rewritten.add(statement);
2127
- }
2128
- }
2129
- for (const statement of statements) {
2130
- if (statement.type === "ImportDeclaration" || rewritten.has(statement)) continue;
2131
- walkIdentifiers(statement, (id, parent) => {
2132
- const alias = aliases.get(id.name);
2133
- if (!alias) return;
2134
- if (parent?.type === "ObjectProperty" && parent.shorthand) source.appendLeft(id.end, `: ${alias}`);
2135
- else source.overwrite(id.start, id.end, alias);
2136
- });
2137
- }
2138
- return source.toString();
2139
- };
2140
- /**
2141
- * REPL 将 ./ 引用按 src 根目录解析,编译结果需要先转成文件表中的完整路径。
2142
- * @param store iframe 预览的文件和编译结果。
2143
- * @param getImportMap 未转换的原始 import map。
2144
- * @param resolve 本次预览共用的最终 URL 转换。
2145
- */
2146
- var bindReplModules = (store, getImportMap = store.getImportMap.bind(store), resolve = require_css.createCdnResolver()) => {
2147
- (0, vue.watch)(() => Object.values(store.files).map((file) => [
2148
- file.filename,
2149
- file.code,
2150
- file.compiled.js,
2151
- file.compiled.ssr
2152
- ]), () => {
2153
- const files = require_css.createFileIndex(Object.fromEntries(Object.entries(store.files).map(([name, file]) => [name, file.code])));
2154
- const importMap = getImportMap();
2155
- const imports = { ...importMap.imports };
2156
- let importsChanged = false;
2157
- for (const file of Object.values(store.files)) for (const output of ["js", "ssr"]) {
2158
- if (!file.compiled[output]) continue;
2159
- try {
2160
- let code = /\.[jt]sx?$/.test(file.filename) ? require_css.transformScript(file.code, file.filename, {
2161
- esm: true,
2162
- jsx: /\.[jt]sx$/.test(file.filename)
2163
- }) : require_css.rewriteImportMeta(file.compiled[output], file.filename);
2164
- code = rewriteImportedExports(code);
2165
- for (const item of [...(0, es_module_lexer.parse)(code)[0]].reverse()) {
2166
- const specifier = moduleSpecifier(item);
2167
- if (!specifier) continue;
2168
- if (require_css.isRelativeSpecifier(specifier)) {
2169
- const resolved = require_css.resolveModulePath(file.filename, specifier, files);
2170
- if (!resolved) continue;
2171
- const target = `./${resolved.replace(/^\/?src\//, "")}`;
2172
- const text = item.type === "dynamic" ? JSON.stringify(target) : target;
2173
- code = code.slice(0, item.start) + text + code.slice(item.end);
2174
- } else if (/^https?:\/\//.test(specifier)) {
2175
- const target = resolve(specifier);
2176
- const text = item.type === "dynamic" ? JSON.stringify(target) : target;
2177
- code = code.slice(0, item.start) + text + code.slice(item.end);
2178
- } else if (!imports[specifier]) {
2179
- imports[specifier] = require_css.resolveNpmImport(specifier);
2180
- importsChanged = true;
2181
- }
2182
- }
2183
- if (file.compiled[output] !== code) file.compiled[output] = code;
2184
- } catch (error) {
2185
- store.errors.push(error instanceof Error ? error : String(error));
2186
- }
2187
- }
2188
- if (importsChanged) store.setImportMap({
2189
- ...importMap,
2190
- imports
2191
- });
2192
- }, { immediate: true });
2193
- };
2194
- //#endregion
2195
- //#region packages/playground/src/core/store.ts
2196
- var DOCS_LINK_IMPORT_CODE = "import { h as __docsH } from \"vue\"";
2197
- var DOCS_LINK_USE_CODE = [
2198
- PREVIEW_SCROLL_RESET_CODE,
2199
- "app.component(\"DocsLink\",{",
2200
- "props:{to:{type:String,default:\"\"}},",
2201
- "setup(props,{slots}){return()=>__docsH(\"a\",{",
2202
- "href:props.to,",
2203
- "style:{color:\"inherit\",textDecoration:\"none\"},",
2204
- "onClick(event){event.preventDefault();window.parent.postMessage({action:\"docs:navigate\",to:props.to},\"*\")}",
2205
- "},slots.default?.())}",
2206
- "})"
2207
- ].join("");
2208
- var escapeHtmlAttr = (value) => value.replace(/&/gu, "&amp;").replace(/"/gu, "&quot;").replace(/'/gu, "&#39;").replace(/</gu, "&lt;").replace(/>/gu, "&gt;");
2209
- /**
2210
- * runtime 画布色。`previewInset` 露出的 padding、iframe chrome 和 sandbox
2211
- * `html`/`body` 必须共用这一变量,暗色下 `--docs-background-color` 与
2212
- * `--vc-background-color-lightest` 不同,否则会出现色带割裂。
2213
- */
2214
- var PLAYGROUND_RUNTIME_CANVAS_BACKGROUND = "var(--vc-background-color-lightest, var(--docs-background-color, #fff))";
2215
- var createRuntimePreviewOptions = (cdnURL, options = {}) => {
2216
- const previewScroller = options.previewScroller === true;
2217
- const resolve = require_css.createCdnResolver(cdnURL);
2218
- const hrefs = Object.values({
2219
- ...require_css.createBuiltinStyles(cdnURL),
2220
- ...getPlaygroundSiteStyles(),
2221
- ...getPlaygroundStyleOverrides()
2222
- }).map(resolve);
2223
- const styleBlocks = [
2224
- `html,body{height:auto;min-height:0;background:${PLAYGROUND_RUNTIME_CANVAS_BACKGROUND}}`,
2225
- "body{color:var(--vc-foreground-color,#080f20)}",
2226
- ...previewScroller ? [PREVIEW_SCROLLER_STYLE] : []
2227
- ];
2228
- return {
2229
- showRuntimeError: false,
2230
- showRuntimeWarning: false,
2231
- headHTML: [
2232
- SANDBOX_RUNTIME_ERROR_CAPTURE_HTML,
2233
- "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
2234
- ...hrefs.map((href) => `<link rel="stylesheet" href="${escapeHtmlAttr(href)}">`),
2235
- "<style>",
2236
- ...styleBlocks,
2237
- "</style>"
2238
- ].join("\n"),
2239
- customCode: {
2240
- importCode: `${DOCS_LINK_IMPORT_CODE}\n${PREVIEW_SERVICE_IMPORT_CODE}`,
2241
- useCode: `${DOCS_LINK_USE_CODE}\n${PREVIEW_SERVICE_USE_CODE}`
2242
- }
2243
- };
2244
- };
2245
- var toReplFilename = (filename) => filename === "import-map.json" || filename.startsWith("src/") ? filename : `src/${filename}`;
2246
- var createReplFile = (filename, code) => {
2247
- const replFilename = toReplFilename(filename);
2248
- return new _vue_repl.File(replFilename, code);
2249
- };
2250
- /**
2251
- * 保留显式配置的模块,供 local 区分宿主默认与 URL 覆盖。
2252
- * @param builtinImportMap 实例层 import map。
2253
- * @returns 按优先级合并的显式模块地址。
2254
- */
2255
- var createRuntimeImportOverrides = (builtinImportMap) => ({
2256
- ...getPlaygroundSiteModules(),
2257
- ...getPlaygroundImportMapOverrides(),
2258
- ...filterSafeHrefs(builtinImportMap?.imports)
2259
- });
2260
- /**
2261
- * 默认 CDN → 站点 modules → 管理页覆盖 → 实例 builtinImportMap。
2262
- * iframe import map 与直接渲染加载的是同一份地址表。
2263
- * @param cdnURL
2264
- * @param builtinImportMap
2265
- */
2266
- var createRuntimeImports = (cdnURL, builtinImportMap) => ({
2267
- ...require_css.createBuiltinImports(cdnURL),
2268
- ...createRuntimeImportOverrides(builtinImportMap)
2269
- });
2270
- var createRuntimeStore = (files, entry, options) => {
2271
- const { cdnURL, builtinImportMap, ...storeOptions } = options;
2272
- const replFiles = (0, vue.ref)(Object.fromEntries(Object.entries(files).map(([filename, code]) => {
2273
- const file = createReplFile(filename, code);
2274
- return [file.filename, file];
2275
- })));
2276
- replFiles.value["import-map.json"] ??= new _vue_repl.File("import-map.json", "{}");
2277
- const mainFile = (0, vue.ref)(toReplFilename(entry));
2278
- const activeFilename = (0, vue.ref)(toReplFilename(entry));
2279
- const imports = createRuntimeImports(cdnURL, builtinImportMap);
2280
- const resolve = require_css.createCdnResolver(cdnURL);
2281
- const resourceLinks = (0, vue.unref)(storeOptions.resourceLinks);
2282
- const store = (0, _vue_repl.useStore)({
2283
- ...storeOptions,
2284
- resourceLinks: (0, vue.ref)({
2285
- ...resourceLinks,
2286
- esModuleShims: resolve(resourceLinks?.esModuleShims || "https://cdn.jsdelivr.net/npm/es-module-shims@1.5.18/dist/es-module-shims.wasm.js?raw")
2287
- }),
2288
- files: replFiles,
2289
- mainFile,
2290
- activeFilename,
2291
- builtinImportMap: (0, vue.ref)({
2292
- ...builtinImportMap,
2293
- imports
2294
- }),
2295
- template: (0, vue.ref)({
2296
- welcomeSFC: files[entry],
2297
- newSFC: require_css.NEW_SFC_CODE
2298
- })
2299
- });
2300
- require_css.bindPlaygroundScss(store);
2301
- const setFiles = store.setFiles.bind(store);
2302
- store.setFiles = (nextFiles, nextEntry) => setFiles({
2303
- "import-map.json": store.files["import-map.json"].code,
2304
- ...nextFiles
2305
- }, nextEntry);
2306
- const getImportMap = store.getImportMap.bind(store);
2307
- bindReplModules(store, getImportMap, resolve);
2308
- store.getImportMap = () => {
2309
- const map = getImportMap();
2310
- const convert = (items = {}) => Object.fromEntries(Object.entries(items).filter((item) => typeof item[1] === "string").map(([id, url]) => [id, resolve(url)]));
2311
- return {
2312
- ...map,
2313
- imports: convert(map.imports),
2314
- scopes: map.scopes && Object.fromEntries(Object.entries(map.scopes).map(([scope, items]) => [resolve(scope), convert(items)]))
2315
- };
2316
- };
2317
- require_css.whenSassReady(files, () => {
2318
- const specifiers = require_css.collectBareSpecifiers(files, Object.keys(files));
2319
- if (!specifiers.length) {
2320
- store.init();
2321
- return;
2322
- }
2323
- store.loading = true;
2324
- const currentImports = {
2325
- ...imports,
2326
- ...filterSafeHrefs(getImportMap().imports)
2327
- };
2328
- resolveReplImports(currentImports, specifiers, resolve).then((resolved) => {
2329
- if (Object.keys(resolved).length !== Object.keys(currentImports).length) store.setImportMap({ imports: resolved }, true);
2330
- store.init();
2331
- }).catch((error) => {
2332
- store.errors.push(error);
2333
- }).finally(() => {
2334
- store.loading = false;
2335
- });
2336
- });
2337
- return store;
2338
- };
2339
- //#endregion
2340
- //#region packages/playground/src/core/runtime/alone/wrapper.vue?vue&type=script&setup=true&lang.ts
2341
- var _hoisted_1$3 = ["aria-label"];
2342
- var _hoisted_2$2 = { class: "docs-playground-popup__frame" };
2343
- var _hoisted_3$2 = { class: "docs-playground-popup__header" };
2344
- var _hoisted_4$2 = ["title"];
2345
- var _hoisted_5$2 = { class: "docs-playground__title-text" };
2346
- var _hoisted_6$1 = {
2347
- key: 0,
2348
- class: "docs-playground__runtime-error",
2349
- role: "alert"
2350
- };
2351
- var _hoisted_7$1 = { class: "docs-playground-popup__body" };
2352
- //#endregion
2353
- //#region packages/playground/src/core/runtime/alone/wrapper.vue
2354
- var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
2355
- __name: "wrapper",
2356
- props: {
2357
- store: {},
2358
- copyValue: {},
2359
- title: { default: "" },
2360
- viewport: { default: "auto" },
2361
- viewportOptions: { default: () => ["auto", 375] },
2362
- previewOptions: {},
2363
- clearConsole: {
2364
- type: Boolean,
2365
- default: true
2366
- },
2367
- local: {
2368
- type: Boolean,
2369
- default: true
2370
- },
2371
- files: { default: () => ({}) },
2372
- entry: { default: "" },
2373
- options: { default: () => ({}) },
2374
- previewScroller: {
2375
- type: Boolean,
2376
- default: false
2377
- }
2378
- },
2379
- emits: [
2380
- "edit",
2381
- "navigate",
2382
- "viewport-change",
2383
- "portal-fulfilled"
2384
- ],
2385
- setup(__props, { emit: __emit }) {
2386
- const LocalSandbox = (0, vue.defineAsyncComponent)(() => Promise.resolve().then(() => local_sandbox_exports));
2387
- const props = __props;
2388
- const emit = __emit;
2389
- const { t } = (0, _deot_docs_locale.useLocale)();
2390
- const currentViewport = (0, vue.ref)(props.viewport);
2391
- const sandboxRef = (0, vue.ref)(null);
2392
- const localError = (0, vue.ref)({
2393
- compile: "",
2394
- runtime: ""
2395
- });
2396
- const handleLocalError = (payload) => {
2397
- localError.value = payload;
2398
- };
2399
- const availableHeight = () => Math.max(0, getWindowInnerSize().height - 40 - 44);
2400
- const temporaryHeight = usePreviewHeightBridge(sandboxRef, availableHeight);
2401
- const sandboxKey = (0, vue.ref)(0);
2402
- const layoutTick = (0, vue.ref)(0);
2403
- const runtimeError = useSandboxRuntimeErrorGuard(sandboxRef);
2404
- useSandboxTheme(sandboxRef);
2405
- const storeErrors = (0, vue.computed)(() => props.local ? localError.value.compile : (props.store?.errors || []).map(toErrorText).filter(Boolean).join("\n"));
2406
- const errorText = (0, vue.computed)(() => {
2407
- const runtime = props.local ? localError.value.runtime : runtimeError.value ? formatSandboxRuntimeError(runtimeError.value, t("playground.runtime.importMapTip")) : "";
2408
- return [storeErrors.value, runtime].filter(Boolean).join("\n\n");
2409
- });
2410
- const layout = (0, vue.computed)(() => {
2411
- layoutTick.value;
2412
- const base = resolvePopupLayout(currentViewport.value);
2413
- if (!temporaryHeight.value) return base;
2414
- const canvasHeight = Math.min(availableHeight(), Math.max(base.canvasHeight, temporaryHeight.value));
2415
- const shellHeight = Math.max(base.shellHeight, canvasHeight + 44);
2416
- return {
2417
- ...base,
2418
- canvasHeight,
2419
- shellHeight,
2420
- bodyHeight: shellHeight - 44
2421
- };
2422
- });
2423
- const shellStyle = (0, vue.computed)(() => ({
2424
- width: `${layout.value.shellWidth}px`,
2425
- height: `${layout.value.shellHeight}px`
2426
- }));
2427
- const canvasStyle = (0, vue.computed)(() => ({
2428
- width: `${layout.value.canvasWidth}px`,
2429
- height: `${layout.value.canvasHeight}px`,
2430
- background: PLAYGROUND_RUNTIME_CANVAS_BACKGROUND
2431
- }));
2432
- const handleRefresh = () => {
2433
- runtimeError.value = "";
2434
- localError.value = {
2435
- compile: "",
2436
- runtime: ""
2437
- };
2438
- sandboxKey.value++;
2439
- };
2440
- const handleViewportChange = (viewport) => {
2441
- currentViewport.value = viewport;
2442
- emit("viewport-change", viewport);
2443
- };
2444
- const handleClose = () => emit("portal-fulfilled");
2445
- const isPlaygroundEditorOpen = () => {
2446
- if (typeof document === "undefined") return false;
2447
- const editor = document.querySelector(".docs-playground-editor__wrapper");
2448
- if (!(editor instanceof HTMLElement)) return false;
2449
- const style = getComputedStyle(editor);
2450
- return style.display !== "none" && style.visibility !== "hidden";
2451
- };
2452
- const handleKeydown = (event) => {
2453
- if (event.key !== "Escape") return;
2454
- if (isPlaygroundEditorOpen()) return;
2455
- event.preventDefault();
2456
- handleClose();
2457
- };
2458
- const handleResize = () => {
2459
- layoutTick.value++;
2460
- };
2461
- const handleBridgeMessage = (event) => {
2462
- if (!isSandboxMessage(sandboxRef.value, event)) return;
2463
- const data = event.data;
2464
- if (!data || data.action !== "docs:navigate" || typeof data.to !== "string") return;
2465
- emit("navigate", data.to);
2466
- };
2467
- (0, vue.watch)(() => props.viewport, (viewport) => {
2468
- currentViewport.value = viewport;
2469
- });
2470
- if (typeof window !== "undefined") {
2471
- window.addEventListener("keydown", handleKeydown);
2472
- window.addEventListener("resize", handleResize);
2473
- window.addEventListener("message", handleBridgeMessage);
2474
- }
2475
- (0, vue.onBeforeUnmount)(() => {
2476
- window.removeEventListener("keydown", handleKeydown);
2477
- window.removeEventListener("resize", handleResize);
2478
- window.removeEventListener("message", handleBridgeMessage);
2479
- });
2480
- return (_ctx, _cache) => {
2481
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2482
- class: "docs-playground-popup",
2483
- role: "dialog",
2484
- "aria-modal": "true",
2485
- "aria-label": (0, vue.unref)(t)("playground.runtime.openPopup")
2486
- }, [(0, vue.createElementVNode)("div", {
2487
- class: "docs-playground-popup__mask",
2488
- "data-action": "popup-mask",
2489
- onClick: handleClose
2490
- }), (0, vue.createElementVNode)("div", {
2491
- class: "docs-playground-popup__shell",
2492
- style: (0, vue.normalizeStyle)(shellStyle.value)
2493
- }, [(0, vue.createElementVNode)("div", _hoisted_2$2, [
2494
- (0, vue.createElementVNode)("div", _hoisted_3$2, [__props.title.trim() ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
2495
- key: 0,
2496
- class: "docs-playground__title",
2497
- title: __props.title.trim()
2498
- }, [(0, vue.createElementVNode)("span", _hoisted_5$2, (0, vue.toDisplayString)(__props.title.trim()), 1)], 8, _hoisted_4$2)) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createVNode)(toolbar_default, {
2499
- "copy-value": __props.copyValue,
2500
- viewport: currentViewport.value,
2501
- "viewport-options": __props.viewportOptions,
2502
- "show-close": true,
2503
- onRefresh: handleRefresh,
2504
- onEdit: _cache[0] || (_cache[0] = ($event) => emit("edit")),
2505
- onClosePopup: handleClose,
2506
- onViewportChange: handleViewportChange
2507
- }, null, 8, [
2508
- "copy-value",
2509
- "viewport",
2510
- "viewport-options"
2511
- ])]),
2512
- errorText.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("pre", _hoisted_6$1, (0, vue.toDisplayString)(errorText.value), 1)) : (0, vue.createCommentVNode)("v-if", true),
2513
- (0, vue.createElementVNode)("div", _hoisted_7$1, [(0, vue.createVNode)((0, vue.unref)(_deot_vc.Scroller), {
2514
- class: "docs-playground-popup__scroller",
2515
- "content-class": "docs-playground-popup__scroller-content",
2516
- "auto-resize": true,
2517
- native: false,
2518
- "show-bar": true,
2519
- height: "100%"
2520
- }, {
2521
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", {
2522
- class: "docs-playground-popup__canvas",
2523
- style: (0, vue.normalizeStyle)(canvasStyle.value)
2524
- }, [__props.local ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(LocalSandbox), {
2525
- key: sandboxKey.value,
2526
- ref_key: "sandboxRef",
2527
- ref: sandboxRef,
2528
- files: __props.files,
2529
- entry: __props.entry,
2530
- options: __props.options,
2531
- "preview-scroller": __props.previewScroller,
2532
- "clear-console": __props.clearConsole,
2533
- onError: handleLocalError
2534
- }, null, 8, [
2535
- "files",
2536
- "entry",
2537
- "options",
2538
- "preview-scroller",
2539
- "clear-console"
2540
- ])) : __props.store ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_vue_repl.Sandbox), {
2541
- key: sandboxKey.value,
2542
- ref_key: "sandboxRef",
2543
- ref: sandboxRef,
2544
- store: __props.store,
2545
- "auto-store-init": false,
2546
- "clear-console": __props.clearConsole,
2547
- "preview-options": __props.previewOptions
2548
- }, null, 8, [
2549
- "store",
2550
- "clear-console",
2551
- "preview-options"
2552
- ])) : (0, vue.createCommentVNode)("v-if", true)], 4)]),
2553
- _: 1
2554
- })])
2555
- ])], 4)], 8, _hoisted_1$3);
2556
- };
2557
- }
2558
- });
2559
- //#endregion
2560
- //#region packages/playground/src/core/runtime/alone/index.ts
2561
- var Alone = new _deot_vc.Portal(wrapper_default, {
2562
- name: "docs-playground-alone",
2563
- leaveDelay: 0,
2564
- multiple: false
2565
- });
2566
- //#endregion
2567
- //#region packages/playground/src/core/runtime/local/playground.ts
2568
- /**
2569
- * 直接渲染与页面同文档,弹层不需要临时撑高。
2570
- * `run` 仍返回可调用函数,但不会申请高度,也不会改动预览尺寸。
2571
- * @param vue 当前预览的 Vue,只用其中的 `isRef`。
2572
- * @returns 注入给预览的 `docs:playground`。
2573
- */
2574
- var createLocalPlayground = (vue) => {
2575
- const run = (_height, optionsOrHandler, lastHandler) => {
2576
- const visible = (typeof optionsOrHandler === "function" || optionsOrHandler == null ? {} : optionsOrHandler).visible;
2577
- const handler = typeof optionsOrHandler === "function" ? optionsOrHandler : typeof lastHandler === "function" ? lastHandler : vue.isRef(visible) ? () => {
2578
- visible.value = true;
2579
- } : void 0;
2580
- return (...args) => {
2581
- if (typeof handler !== "function") return void 0;
2582
- return handler(...args);
2583
- };
2584
- };
2585
- return {
2586
- enable: () => Promise.resolve(true),
2587
- disable() {},
2588
- dispose() {},
2589
- run
2590
- };
2591
- };
2592
- //#endregion
2593
- //#region packages/playground/src/core/runtime/local/port.ts
2594
- /** 只在桥接 iframe 内部转发,保证父页面收到的 source 是该 iframe。 */
2595
- var LOCAL_BRIDGE_CHANNEL = "docs:local-out";
2596
- var BRIDGE_INSTALLER = `
2597
- if (!window.__docsLocalBridge) {
2598
- window.__docsLocalBridge = true;
2599
- window.addEventListener('message', function (event) {
2600
- var data = event.data;
2601
- if (!data || data.channel !== ${JSON.stringify(LOCAL_BRIDGE_CHANNEL)}) return;
2602
- window.parent.postMessage(data.payload, '*');
2603
- });
2604
- }
2605
- `;
2606
- var LOCAL_BRIDGE_SRCDOC = `<!doctype html><html><head></head><body><script>${BRIDGE_INSTALLER}<\/script></body></html>`;
2607
- /**
2608
- * 在同源空白 iframe 里安装转发脚本。调用发生在 iframe realm,父页面看到的 source 才是它。
2609
- * @param iframe 用作消息身份的空白 iframe。
2610
- * @returns 脚本是否已经装上。
2611
- */
2612
- var installLocalBridge = (iframe) => {
2613
- const win = iframe.contentWindow;
2614
- if (!win || win.__docsLocalBridge) return Boolean(win?.__docsLocalBridge);
2615
- win.eval(BRIDGE_INSTALLER);
2616
- return Boolean(win.__docsLocalBridge);
2617
- };
2618
- /**
2619
- * 满足 PlaygroundService 现有 Window 调用的预览窗口。
2620
- * 出站消息经 iframe 转发,入站回复再交回服务,协议仍是 docs:height / docs:navigate。
2621
- */
2622
- var LocalPreviewPort = class {
2623
- iframe;
2624
- parent;
2625
- listeners = /* @__PURE__ */ new Map();
2626
- constructor(iframe) {
2627
- this.iframe = iframe;
2628
- this.parent = { postMessage: (data) => {
2629
- this.iframe.contentWindow?.postMessage({
2630
- channel: LOCAL_BRIDGE_CHANNEL,
2631
- payload: data
2632
- }, "*");
2633
- } };
2634
- }
2635
- get contentWindow() {
2636
- return this.iframe.contentWindow;
2637
- }
2638
- get innerHeight() {
2639
- const fromWindow = this.iframe.contentWindow?.innerHeight ?? 0;
2640
- if (fromWindow > 0) return fromWindow;
2641
- return this.iframe.clientHeight || this.iframe.parentElement?.clientHeight || 0;
2642
- }
2643
- addEventListener(type, listener) {
2644
- const win = this.iframe.contentWindow;
2645
- if (!win) return;
2646
- if (type !== "message") {
2647
- win.addEventListener(type, listener);
2648
- return;
2649
- }
2650
- const wrapped = (event) => {
2651
- const message = event;
2652
- const data = message.data;
2653
- if (data && data.channel === "docs:local-out") return;
2654
- listener({
2655
- data: message.data,
2656
- source: this.parent
2657
- });
2658
- };
2659
- this.listeners.set(listener, wrapped);
2660
- win.addEventListener("message", wrapped);
2661
- }
2662
- removeEventListener(type, listener) {
2663
- const win = this.iframe.contentWindow;
2664
- if (!win) return;
2665
- const wrapped = this.listeners.get(listener);
2666
- if (wrapped) {
2667
- this.listeners.delete(listener);
2668
- win.removeEventListener(type, wrapped);
2669
- return;
2670
- }
2671
- win.removeEventListener(type, listener);
2672
- }
2673
- requestAnimationFrame(callback) {
2674
- return window.requestAnimationFrame(callback);
2675
- }
2676
- cancelAnimationFrame(handle) {
2677
- window.cancelAnimationFrame(handle);
2678
- }
2679
- setTimeout(handler, timeout, ...args) {
2680
- return window.setTimeout(handler, timeout, ...args);
2681
- }
2682
- clearTimeout(handle) {
2683
- window.clearTimeout(handle);
2684
- }
2685
- };
2686
- //#endregion
2687
- //#region packages/playground/src/core/runtime/local/format-error.ts
2688
- /**
2689
- * 空 reject(例如弹层取消)不要渲染成红条。
2690
- * @param error 运行时抛出的值。
2691
- * @returns 可展示的错误文本;空取消返回空字符串。
2692
- */
2693
- var formatPlaygroundRuntimeError = (error) => {
2694
- if (error == null) return "";
2695
- const raw = typeof error === "object" && "message" in error ? error.message : error;
2696
- if (raw == null || raw === "") return "";
2697
- const text = String(raw);
2698
- if (text === "undefined" || text === "null") return "";
2699
- return text;
2700
- };
2701
- //#endregion
2702
- //#region packages/playground/src/core/runtime/local/builtins.ts
2703
- var loaders = {
2704
- "vue": () => import("vue"),
2705
- "vue-router": () => import("vue-router"),
2706
- "pinia": async () => pinia,
2707
- "@deot/helper": () => import("@deot/helper"),
2708
- "@deot/helper-cache": () => import("@deot/helper-cache"),
2709
- "@deot/helper-device": () => import("@deot/helper-device"),
2710
- "@deot/helper-dom": () => import("@deot/helper-dom"),
2711
- "@deot/helper-emitter": () => import("@deot/helper-emitter"),
2712
- "@deot/helper-fp": () => import("@deot/helper-fp"),
2713
- "@deot/helper-is": () => import("@deot/helper-is"),
2714
- "@deot/helper-load": () => import("@deot/helper-load"),
2715
- "@deot/helper-resize": () => import("@deot/helper-resize"),
2716
- "@deot/helper-route": () => import("@deot/helper-route"),
2717
- "@deot/helper-scheduler": () => import("@deot/helper-scheduler"),
2718
- "@deot/helper-shared": () => import("@deot/helper-shared"),
2719
- "@deot/helper-unicode": () => import("@deot/helper-unicode"),
2720
- "@deot/helper-utils": () => import("@deot/helper-utils"),
2721
- "@deot/helper-validator": () => import("@deot/helper-validator"),
2722
- "@deot/helper-wheel": () => import("@deot/helper-wheel"),
2723
- "@deot/http-client": () => import("@deot/http-client"),
2724
- "@deot/http-core": () => import("@deot/http-core"),
2725
- "@deot/http-hooks": () => import("@deot/http-hooks"),
2726
- "@deot/http": () => import("@deot/http"),
2727
- "@deot/vc": async () => _deot_vc,
2728
- "@deot/vc-components": async () => _deot_vc_components,
2729
- "@deot/vc-hooks": async () => _deot_vc_hooks,
2730
- "@deot/vc-locale": () => import("@deot/vc-locale"),
2731
- "lodash-es": () => import("lodash-es"),
2732
- "echarts": () => import("echarts"),
2733
- "photoswipe": () => import("photoswipe"),
2734
- "jszip": () => import("jszip"),
2735
- "jspdf": () => import("jspdf"),
2736
- "dayjs": async () => (await import("dayjs")).default
2737
- };
2738
- var cache = /* @__PURE__ */ new Map();
2739
- /** 直接渲染可从宿主包注入的说明符;与 Vue 无关的大型依赖按需加载。 */
2740
- var localBuiltinIds = new Set(Object.keys(loaders));
2741
- var ensureLocalBuiltin = (id) => {
2742
- const loader = loaders[id];
2743
- if (!loader) return Promise.reject(/* @__PURE__ */ new Error(`[Playground] 没有本地模块 "${id}"`));
2744
- let pending = cache.get(id);
2745
- if (!pending) {
2746
- pending = loader();
2747
- cache.set(id, pending);
2748
- }
2749
- return pending;
2750
- };
2751
- //#endregion
2752
- //#region packages/playground/src/core/runtime/local/modules.ts
2753
- var SKIP_COPY_KEYS = /* @__PURE__ */ new Set([
2754
- "prototype",
2755
- "caller",
2756
- "arguments"
2757
- ]);
2758
- var toHostModule = (ns) => {
2759
- if (ns == null || typeof ns !== "object" && typeof ns !== "function") return {
2760
- __esModule: true,
2761
- default: ns
2762
- };
2763
- const source = ns;
2764
- const mod = { __esModule: true };
2765
- const assignKey = (key) => {
2766
- if (SKIP_COPY_KEYS.has(key) || key === "__esModule") return;
2767
- try {
2768
- mod[key] = source[key];
2769
- } catch {}
2770
- };
2771
- try {
2772
- Object.keys(source).forEach(assignKey);
2773
- } catch {}
2774
- try {
2775
- Object.getOwnPropertyNames(source).forEach(assignKey);
2776
- } catch {}
2777
- if (!("default" in mod) || mod.default === void 0) mod.default = source.default !== void 0 ? source.default : source;
2778
- return mod;
2779
- };
2780
- var lookupHostModule = (id, modules) => {
2781
- if (Object.prototype.hasOwnProperty.call(modules, id)) return toHostModule(modules[id]);
2782
- return null;
2783
- };
2784
- var pickNestedExport = (rootMod, rest) => {
2785
- if (!rest) return void 0;
2786
- const parts = rest.split("/").filter(Boolean);
2787
- let current = rootMod;
2788
- for (const part of parts) {
2789
- if (current == null || typeof current !== "object" && typeof current !== "function") return;
2790
- const record = current;
2791
- if (!(part in record)) return void 0;
2792
- current = record[part];
2793
- }
2794
- return current;
2795
- };
2796
- /**
2797
- * 先精确匹配,再从右往左剥路径,把剩余段当成导出属性。
2798
- * 模块表只来自当前 Playground,不写入全局注册表。
2799
- * @param id 裸模块说明符。
2800
- * @param modules 当前 Playground 的模块表。
2801
- * @returns 可被 `require` 使用的模块;找不到时返回 null。
2802
- */
2803
- var resolveHostModule = (id, modules) => {
2804
- const exact = lookupHostModule(id, modules);
2805
- if (exact) return exact;
2806
- let cursor = id;
2807
- while (true) {
2808
- const slash = cursor.lastIndexOf("/");
2809
- if (slash <= 0) break;
2810
- const parent = cursor.slice(0, slash);
2811
- const rest = id.slice(parent.length + 1);
2812
- const rootMod = lookupHostModule(parent, modules);
2813
- if (rootMod) {
2814
- const nested = pickNestedExport(rootMod, rest);
2815
- if (nested !== void 0) return toHostModule(nested);
2816
- }
2817
- cursor = parent;
2818
- }
2819
- return null;
2820
- };
2821
- var HOST_VUE_SPECIFIERS = /* @__PURE__ */ new Set(["vue", "vue/server-renderer"]);
2822
- var VUE_DEPENDENT_BUILTINS = /* @__PURE__ */ new Set([
2823
- "vue/server-renderer",
2824
- "vue-router",
2825
- "pinia",
2826
- "@deot/vc",
2827
- "@deot/vc-components",
2828
- "@deot/vc-hooks",
2829
- "@deot/vc-locale"
2830
- ]);
2831
- var messageOf = (error) => error instanceof Error ? error.message : String(error);
2832
- /**
2833
- * 找到 import map 里能覆盖该说明符的键。子路径没有独立 URL 时回退到父包。
2834
- * @param id 裸模块说明符。
2835
- * @param imports 合并后的 import map。
2836
- * @returns 命中的 import map 键;没有地址时返回 null。
2837
- */
2838
- var resolveImportTarget = (id, imports) => {
2839
- if (imports[id]) return id;
2840
- let cursor = id;
2841
- while (true) {
2842
- const slash = cursor.lastIndexOf("/");
2843
- if (slash <= 0) return null;
2844
- const parent = cursor.slice(0, slash);
2845
- if (imports[parent]) return parent;
2846
- cursor = parent;
2847
- }
2848
- };
2849
- var prefersLocalBuiltin = (id, imports, overrides = new Set(Object.keys(imports))) => {
2850
- if (!localBuiltinIds.has(id) && !HOST_VUE_SPECIFIERS.has(id)) return false;
2851
- if (overrides.has(id)) return false;
2852
- return !VUE_DEPENDENT_BUILTINS.has(id) || !overrides.has("vue");
2853
- };
2854
- /**
2855
- * 内置依赖默认注入宿主副本,显式 URL 按模块覆盖,包括与默认 CDN 相同的地址。
2856
- * @param specifiers 本次预览用到的裸模块说明符。
2857
- * @param imports 合并后的 import map。
2858
- * @param builtins 宿主 Vue 与 vue-router。
2859
- * @param builtins.vue 当前页面的 Vue 模块。
2860
- * @param builtins.vueRouter 打包进来的 vue-router,可被 import map 覆盖。
2861
- * @param loader 按 URL 加载远程模块。
2862
- * @param overrides 显式配置的模块名,与自动补齐的默认地址区分。
2863
- * @returns 模块表和加载错误。
2864
- */
2865
- var loadLocalModules = async (specifiers, imports, builtins, loader, overrides = new Set(Object.keys(imports))) => {
2866
- const modules = {
2867
- "vue": builtins.vue,
2868
- "vue-router": builtins.vueRouter
2869
- };
2870
- const errors = [];
2871
- const targets = /* @__PURE__ */ new Set();
2872
- const localIds = /* @__PURE__ */ new Set();
2873
- specifiers.forEach((specifier) => {
2874
- let local = false;
2875
- if (prefersLocalBuiltin(specifier, imports, overrides)) localIds.add(specifier);
2876
- else {
2877
- let cursor = specifier;
2878
- while (true) {
2879
- const slash = cursor.lastIndexOf("/");
2880
- if (slash <= 0) break;
2881
- const parent = cursor.slice(0, slash);
2882
- if (prefersLocalBuiltin(parent, imports, overrides)) {
2883
- localIds.add(parent);
2884
- local = true;
2885
- break;
2886
- }
2887
- cursor = parent;
2888
- }
2889
- }
2890
- const target = resolveImportTarget(specifier, imports);
2891
- if (target && prefersLocalBuiltin(target, imports, overrides)) localIds.add(target);
2892
- else if (target) targets.add(target);
2893
- else if (!local && !localIds.has(specifier)) {
2894
- imports[specifier] = require_css.resolveNpmImport(specifier);
2895
- targets.add(specifier);
2896
- }
2897
- });
2898
- await Promise.all([...localIds].map(async (id) => {
2899
- if (id === "vue" || id === "vue-router") return;
2900
- if (id === "vue/server-renderer") {
2901
- modules[id] = builtins.vue;
2902
- return;
2903
- }
2904
- modules[id] = await ensureLocalBuiltin(id);
2905
- }));
2906
- for (const id of targets) {
2907
- const url = imports[id];
2908
- if (!url || prefersLocalBuiltin(id, imports, overrides)) continue;
2909
- try {
2910
- modules[id] = await loader(url);
2911
- } catch (error) {
2912
- errors.push(`[Playground] 无法加载模块 "${id}" (${url}): ${messageOf(error)}`);
2913
- }
2914
- }
2915
- return {
2916
- modules,
2917
- errors
2918
- };
2919
- };
2920
- //#endregion
2921
- //#region packages/playground/src/core/runtime/local/vue-guard.ts
2922
- /**
2923
- * 给 files 注入选定的 Vue,但 `createApp().mount` 不会卸掉页面。
2924
- * 防护按本次 link 创建,多个 Playground 互不覆盖。
2925
- * @param capture 本次链接收集到的根组件、局部组件和 provide。
2926
- * @param vue 当前预览选定的 Vue。
2927
- * @returns 带防护的 Vue 模块。
2928
- */
2929
- var createGuardedVue = (capture, vue$4 = vue) => {
2930
- const createApp = (rootComponent, rootProps) => {
2931
- const fakeApp = {
2932
- _component: rootComponent,
2933
- _props: rootProps,
2934
- config: {
2935
- errorHandler: null,
2936
- warnHandler: null,
2937
- globalProperties: {}
2938
- },
2939
- _context: {
2940
- provides: Object.create(null),
2941
- components: Object.create(null),
2942
- directives: Object.create(null),
2943
- mixins: []
2944
- },
2945
- use() {
2946
- return fakeApp;
2947
- },
2948
- mixin(mixin) {
2949
- fakeApp._context.mixins.push(mixin);
2950
- return fakeApp;
2951
- },
2952
- component(name, component) {
2953
- if (component) {
2954
- fakeApp._context.components[name] = component;
2955
- return fakeApp;
2956
- }
2957
- return fakeApp._context.components[name];
2958
- },
2959
- directive() {
2960
- return fakeApp;
2961
- },
2962
- provide(key, value) {
2963
- fakeApp._context.provides[key] = value;
2964
- return fakeApp;
2965
- },
2966
- mount() {
2967
- capture.components = { ...fakeApp._context.components };
2968
- capture.provides = { ...fakeApp._context.provides };
2969
- capture.mountedComponent = vue$4.defineComponent({
2970
- name: "PlaygroundCapturedApp",
2971
- components: fakeApp._context.components,
2972
- setup() {
2973
- Reflect.ownKeys(fakeApp._context.provides).forEach((key) => {
2974
- vue$4.provide(key, fakeApp._context.provides[key]);
2975
- });
2976
- return () => vue$4.h(rootComponent, rootProps || null);
2977
- }
2978
- });
2979
- return capture.mountedComponent;
2980
- },
2981
- unmount() {},
2982
- onUnmount() {
2983
- return fakeApp;
2984
- }
2985
- };
2986
- return fakeApp;
2987
- };
2988
- return new Proxy(vue$4, { get(target, prop, receiver) {
2989
- if (prop === "createApp" || prop === "createSSRApp") return createApp;
2990
- if (prop === "default") return receiver;
2991
- return Reflect.get(target, prop, receiver);
2992
- } });
2993
- };
2994
- //#endregion
2995
- //#region packages/playground/src/core/runtime/local/linker.ts
2996
- /**
2997
- * 在当前页面链接编译结果,返回入口组件。
2998
- * 给选定的 Vue 加上 mount 防护,避免字符串入口卸掉页面。
2999
- * @param compiled 浏览器编译结果。
3000
- * @param files 当前 Playground 文件表。
3001
- * @param options 可选的宿主模块表。
3002
- * @returns 入口组件、样式和链接错误。
3003
- */
3004
- var linkPlayground = (compiled, files, options = {}) => {
3005
- const errors = [...compiled.errors || []];
3006
- if (!compiled.entry || errors.length) return {
3007
- component: null,
3008
- css: compiled.css || [],
3009
- errors
3010
- };
3011
- const capture = {
3012
- mountedComponent: null,
3013
- components: {},
3014
- provides: {}
3015
- };
3016
- const modules = { ...options.modules || {} };
3017
- modules.vue = createGuardedVue(capture, modules.vue || vue);
3018
- const fileIndex = require_css.createFileIndex(files);
3019
- const root = require_css.getCommonRoot([...fileIndex.keys()]);
3020
- const cache = /* @__PURE__ */ new Map();
3021
- const visiting = /* @__PURE__ */ new Set();
3022
- const requireFn = (fromFilename) => (id) => {
3023
- if (require_css.isRelativeSpecifier(id)) {
3024
- const resolved = require_css.resolveModulePath(fromFilename, id, fileIndex);
3025
- if (!resolved) throw new Error(`[Playground] 无法解析相对模块 "${id}"(来自 ${fromFilename})`);
3026
- /* istanbul ignore if -- 公共根已包含全部 files,正常解析不会逃出 */
3027
- if (!require_css.isInsideRoot(resolved, root)) throw new Error(`[Playground] 模块逃出虚拟根: ${resolved}`);
3028
- return loadModule(resolved);
3029
- }
3030
- const host = resolveHostModule(id, modules);
3031
- if (host) return host;
3032
- throw new Error(`[Playground] 未注册的模块 "${id}"。请在 import map 中提供可加载的地址。`);
3033
- };
3034
- const loadModule = (filename) => {
3035
- const key = require_css.normalizeVirtualPath(filename);
3036
- if (cache.has(key)) return cache.get(key);
3037
- if (visiting.has(key)) return cache.get(key);
3038
- const mod = compiled.modules[key];
3039
- if (!mod) throw new Error(`[Playground] 缺少编译模块: ${key}`);
3040
- const module = { exports: {} };
3041
- cache.set(key, module.exports);
3042
- visiting.add(key);
3043
- try {
3044
- new Function("require", "module", "exports", mod.js)(requireFn(key), module, module.exports);
3045
- cache.set(key, module.exports);
3046
- return module.exports;
3047
- } catch (error) {
3048
- const message = error instanceof Error ? error.message : String(error);
3049
- errors.push(`[Playground] 执行失败 ${key}: ${message}`);
3050
- throw error;
3051
- } finally {
3052
- visiting.delete(key);
3053
- }
3054
- };
3055
- try {
3056
- const entryExports = loadModule(compiled.entry);
3057
- let component = entryExports?.default ?? entryExports;
3058
- if (capture.mountedComponent) component = capture.mountedComponent;
3059
- /* istanbul ignore if -- 空入口在编译阶段已经报错 */
3060
- if (!component) {
3061
- errors.push("[Playground] 入口必须 export default 组件,或调用 createApp(App).mount()");
3062
- return {
3063
- component: null,
3064
- css: compiled.css || [],
3065
- errors
3066
- };
3067
- }
3068
- return {
3069
- component,
3070
- css: compiled.css || [],
3071
- errors
3072
- };
3073
- } catch (error) {
3074
- if (!errors.length) errors.push(error instanceof Error ? error.message : String(error));
3075
- return {
3076
- component: null,
3077
- css: compiled.css || [],
3078
- errors
3079
- };
3080
- }
3081
- };
3082
- //#endregion
3083
- //#region packages/playground/src/core/runtime/local/remote.ts
3084
- var HOST_VUE = /* @__PURE__ */ new Set(["vue", "vue/server-renderer"]);
3085
- var moduleUrl = (url) => new URL(url, globalThis.location?.href).href;
3086
- var REMOTE_SOURCE_CACHE_LIMIT = 128;
3087
- var sourceCache = /* @__PURE__ */ new Map();
3088
- var prepareRemoteSource = async (url) => {
3089
- const response = await fetch(url);
3090
- if (!response.ok) throw new Error(`${response.status} ${url}`);
3091
- const text = await response.text();
3092
- await (0, es_module_lexer.init)();
3093
- const [specifiers] = (0, es_module_lexer.parse)(text);
3094
- let source = text;
3095
- for (const item of [...specifiers].reverse()) if (item.type === "dynamic") source = source.slice(0, item.importStart) + "__import" + source.slice(item.importStart + 6);
3096
- const cjs = (0, sucrase.transform)(require_css.rewriteImportMeta(source, url), {
3097
- transforms: ["imports"],
3098
- disableESTransforms: true,
3099
- production: true,
3100
- filePath: url
3101
- }).code;
3102
- return {
3103
- dependencies: specifiers.filter((item) => item.type === "static" || item.type === "reexport-star").map((item) => item.specifier),
3104
- run: new Function("require", "module", "exports", "__import", cjs)
3105
- };
3106
- };
3107
- var readRemoteSource = (url) => {
3108
- if (new URL(url).origin === globalThis.location?.origin) return prepareRemoteSource(url);
3109
- const cached = sourceCache.get(url);
3110
- if (cached) {
3111
- sourceCache.delete(url);
3112
- sourceCache.set(url, cached);
3113
- return cached;
3114
- }
3115
- const task = prepareRemoteSource(url).catch((error) => {
3116
- if (sourceCache.get(url) === task) sourceCache.delete(url);
3117
- throw error;
3118
- });
3119
- sourceCache.set(url, task);
3120
- if (sourceCache.size > REMOTE_SOURCE_CACHE_LIMIT) sourceCache.delete(sourceCache.keys().next().value);
3121
- return task;
3122
- };
3123
- /**
3124
- * 把 CDN 上的 ESM 先拉完整张依赖图,再在当前页面里执行。
3125
- * 裸导入优先走 import map,缺失的 npm 依赖回退到 CDN ESM;import() 按需加载。
3126
- * @param imports 合并后的 import map。
3127
- * @param builtins 宿主 Vue 与 vue-router。
3128
- * @param overrides 显式配置的模块名,与自动补齐的默认地址区分。
3129
- * @param importModule 浏览器原生 ESM 加载器。
3130
- * @param cdnURL 没有 launch 选点结果时使用的实例 CDN。
3131
- * @returns 按 URL 加载并执行远程模块的加载器。
3132
- */
3133
- var createRemoteLoader = (imports, builtins, overrides = new Set(Object.keys(imports)), importModule = (url) => import(
3134
- /* @vite-ignore */
3135
- url
3136
- ), cdnURL) => {
3137
- const resolve = require_css.createCdnResolver(cdnURL);
3138
- const resolveUrl = (url) => resolve(moduleUrl(url));
3139
- const hostModules = {
3140
- "vue": builtins.vue,
3141
- "vue-router": builtins.vueRouter
3142
- };
3143
- const pendingBuiltins = /* @__PURE__ */ new Set();
3144
- const sources = /* @__PURE__ */ new Map();
3145
- const pendingSources = /* @__PURE__ */ new Map();
3146
- const evaluated = /* @__PURE__ */ new Map();
3147
- const nativeModules = /* @__PURE__ */ new Map();
3148
- const loadNative = (url) => {
3149
- let pending = nativeModules.get(url);
3150
- if (!pending) {
3151
- pending = importModule(url).then((module) => {
3152
- const exports = toHostModule(module);
3153
- evaluated.set(url, exports);
3154
- return exports;
3155
- });
3156
- nativeModules.set(url, pending);
3157
- }
3158
- return pending;
3159
- };
3160
- const localBuiltinId = (id) => {
3161
- if (imports[id] && !prefersLocalBuiltin(id, imports, overrides)) return null;
3162
- if (prefersLocalBuiltin(id, imports, overrides)) return id;
3163
- let cursor = id;
3164
- while (true) {
3165
- const slash = cursor.lastIndexOf("/");
3166
- if (slash <= 0) return null;
3167
- const parent = cursor.slice(0, slash);
3168
- if (prefersLocalBuiltin(parent, imports, overrides)) return parent;
3169
- cursor = parent;
3170
- }
3171
- };
3172
- const dependencySpecifier = (from, specifier) => {
3173
- if (require_css.isRelativeSpecifier(specifier) || /^https?:\/\//.test(specifier)) {
3174
- const url = new URL(specifier, from);
3175
- const name = require_css.cdnModuleName(url.href);
3176
- if (name && (imports[name] || localBuiltinIds.has(name))) return name;
3177
- return url.href;
3178
- }
3179
- return specifier;
3180
- };
3181
- const dependencyUrl = (from, id) => {
3182
- const specifier = dependencySpecifier(from, id);
3183
- if (/^https?:\/\//.test(specifier)) return resolveUrl(specifier);
3184
- if (imports[specifier] && !prefersLocalBuiltin(specifier, imports, overrides)) return resolveUrl(imports[specifier]);
3185
- if (HOST_VUE.has(specifier) && prefersLocalBuiltin(specifier, imports, overrides)) return null;
3186
- let cursor = specifier;
3187
- while (cursor) {
3188
- if (localBuiltinIds.has(cursor) && prefersLocalBuiltin(cursor, imports, overrides)) {
3189
- pendingBuiltins.add(cursor);
3190
- return null;
3191
- }
3192
- const slash = cursor.lastIndexOf("/");
3193
- if (slash <= 0) break;
3194
- cursor = cursor.slice(0, slash);
3195
- }
3196
- const target = imports[specifier] ? specifier : resolveImportTarget(specifier, imports);
3197
- if (!target) {
3198
- imports[specifier] = require_css.resolveNpmImport(specifier);
3199
- return resolveUrl(imports[specifier]);
3200
- }
3201
- return resolveUrl(imports[target]);
3202
- };
3203
- const crawl = async (url, visited = /* @__PURE__ */ new Set()) => {
3204
- if (!overrides.size && require_css.isCdnEsmURL(url)) {
3205
- await loadNative(url);
3206
- return;
3207
- }
3208
- if (visited.has(url)) return;
3209
- visited.add(url);
3210
- let pending = pendingSources.get(url);
3211
- if (!pending) {
3212
- pending = readRemoteSource(url);
3213
- pendingSources.set(url, pending);
3214
- }
3215
- const source = await pending;
3216
- sources.set(url, source);
3217
- await Promise.all(source.dependencies.map(async (specifier) => {
3218
- const next = dependencyUrl(url, specifier);
3219
- if (next) await crawl(next, visited);
3220
- }));
3221
- };
3222
- const requireFrom = (from) => (specifier) => {
3223
- const id = dependencySpecifier(from, specifier);
3224
- if (HOST_VUE.has(id) && prefersLocalBuiltin(id, imports, overrides)) return toHostModule(builtins.vue);
3225
- const builtinId = localBuiltinId(id);
3226
- if (builtinId) return toHostModule(hostModules[builtinId]);
3227
- if (require_css.isRelativeSpecifier(id) || /^https?:\/\//.test(id)) return evaluate(dependencyUrl(from, id));
3228
- const target = imports[id] ? id : resolveImportTarget(id, imports);
3229
- if (!target || !imports[target]) throw new Error(`[Playground] 未注册的模块 "${id}"`);
3230
- const loaded = evaluate(resolveUrl(imports[target]));
3231
- if (target === id) return loaded;
3232
- if (!resolveHostModule(id, { [target]: loaded }))
3233
- /* istanbul ignore next -- 父包没有对应导出时才拒绝 */
3234
- throw new Error(`[Playground] 未注册的模块 "${id}"`);
3235
- return loaded;
3236
- };
3237
- const evaluate = (url) => {
3238
- if (evaluated.has(url)) return evaluated.get(url);
3239
- const source = sources.get(url);
3240
- if (!source) throw new Error(`[Playground] 远程模块尚未下载: ${url}`);
3241
- const module = { exports: {} };
3242
- evaluated.set(url, module.exports);
3243
- const runner = source.run;
3244
- runner(requireFrom(url), module, module.exports, async (id) => {
3245
- const next = dependencyUrl(url, id);
3246
- if (next) await load(next);
3247
- else {
3248
- const builtinId = localBuiltinId(dependencySpecifier(url, id));
3249
- if (builtinId && !(builtinId in hostModules)) hostModules[builtinId] = await ensureLocalBuiltin(builtinId);
3250
- }
3251
- return requireFrom(url)(id);
3252
- });
3253
- evaluated.set(url, module.exports);
3254
- return module.exports;
3255
- };
3256
- const loading = /* @__PURE__ */ new Map();
3257
- const load = (sourceUrl) => {
3258
- const url = resolveUrl(sourceUrl);
3259
- const pending = loading.get(url);
3260
- if (pending) return pending;
3261
- const task = (async () => {
3262
- await crawl(url);
3263
- await Promise.all([...pendingBuiltins].map(async (id) => {
3264
- if (id === "vue" || id === "vue-router") return;
3265
- hostModules[id] = await ensureLocalBuiltin(id);
3266
- }));
3267
- return evaluate(url);
3268
- })();
3269
- loading.set(url, task);
3270
- return task;
3271
- };
3272
- return { load };
3273
- };
3274
- //#endregion
3275
- //#region packages/playground/src/core/runtime/local/local-sandbox.vue?vue&type=script&setup=true&lang.ts
3276
- var _hoisted_1$2 = ["srcdoc"];
3277
- var local_sandbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ (0, vue.defineComponent)({
3278
- __name: "local-sandbox",
3279
- props: {
3280
- files: {},
3281
- entry: {},
3282
- options: {},
3283
- previewScroller: {
3284
- type: Boolean,
3285
- default: false
3286
- },
3287
- clearConsole: {
3288
- type: Boolean,
3289
- default: false
3290
- },
3291
- autoHeight: {
3292
- type: Boolean,
3293
- default: false
3294
- }
3295
- },
3296
- emits: ["error"],
3297
- setup(__props, { expose: __expose, emit: __emit }) {
3298
- const props = __props;
3299
- const emit = __emit;
3300
- const bridgeSrcdoc = LOCAL_BRIDGE_SRCDOC;
3301
- const containerEl = (0, vue.ref)(null);
3302
- const bridgeEl = (0, vue.ref)(null);
3303
- const mountEl = (0, vue.ref)(null);
3304
- const compileError = (0, vue.ref)("");
3305
- const runtimeError = (0, vue.ref)("");
3306
- const messageSource = (0, vue.shallowRef)(null);
3307
- let port = null;
3308
- let previewApp = null;
3309
- let version = 0;
3310
- let pendingMount = null;
3311
- const styleId = `docs-local-${Math.random().toString(36).slice(2, 10)}`;
3312
- const publishError = () => {
3313
- emit("error", {
3314
- compile: compileError.value,
3315
- runtime: runtimeError.value
3316
- });
3317
- };
3318
- (0, vue.watch)([compileError, runtimeError], publishError);
3319
- const createDocsLink = (preview, vue$2) => vue$2.defineComponent({
3320
- name: "DocsLink",
3321
- props: { to: {
3322
- type: String,
3323
- default: ""
3324
- } },
3325
- setup(linkProps, { slots }) {
3326
- return () => vue$2.h("a", {
3327
- href: linkProps.to,
3328
- style: {
3329
- color: "inherit",
3330
- textDecoration: "none"
3331
- },
3332
- onClick(event) {
3333
- event.preventDefault();
3334
- preview.parent.postMessage({
3335
- action: "docs:navigate",
3336
- to: linkProps.to
3337
- });
3338
- }
3339
- }, slots.default?.());
3340
- }
3341
- });
3342
- const destroyPreview = () => {
3343
- previewApp?.unmount();
3344
- previewApp = null;
3345
- require_css.removeStyle(styleId);
3346
- };
3347
- const mountPreview = (job, preview) => {
3348
- if (!mountEl.value) return;
3349
- const { component, vue: vue$3, scroller, css } = job;
3350
- destroyPreview();
3351
- require_css.injectStyle(styleId, css);
3352
- if (props.clearConsole) console.clear();
3353
- const app = vue$3.createApp({
3354
- name: "LocalSandboxRoot",
3355
- setup() {
3356
- const service = createLocalPlayground(vue$3);
3357
- vue$3.provide("docs:playground", service);
3358
- vue$3.onErrorCaptured((error) => {
3359
- const message = formatPlaygroundRuntimeError(error);
3360
- if (message) runtimeError.value = message;
3361
- return false;
3362
- });
3363
- vue$3.onBeforeUnmount(() => service.dispose());
3364
- return () => {
3365
- const content = vue$3.h(component);
3366
- if (!scroller) return content;
3367
- return vue$3.h(scroller, {
3368
- height: "100%",
3369
- native: false,
3370
- autoResize: true,
3371
- showBar: true,
3372
- contentClass: PREVIEW_SCROLL_CONTENT_CLASS
3373
- }, { default: () => content });
3374
- };
3375
- }
3376
- });
3377
- app.component("DocsLink", createDocsLink(preview, vue$3));
3378
- app.config.errorHandler = (error) => {
3379
- const message = formatPlaygroundRuntimeError(error);
3380
- if (message) runtimeError.value = message;
3381
- };
3382
- app.mount(mountEl.value);
3383
- previewApp = app;
3384
- };
3385
- const ensurePort = () => {
3386
- const iframe = bridgeEl.value;
3387
- if (!iframe?.contentWindow) return null;
3388
- try {
3389
- if (!installLocalBridge(iframe)) return null;
3390
- } catch {
3391
- return null;
3392
- }
3393
- port ??= new LocalPreviewPort(iframe);
3394
- messageSource.value = iframe.contentWindow;
3395
- return port;
3396
- };
3397
- const tryMount = async () => {
3398
- const job = pendingMount;
3399
- /* istanbul ignore if -- 桥接 iframe 尚未就绪或编译已被取代 */
3400
- if (!job || job.version !== version) return;
3401
- const preview = ensurePort();
3402
- /* istanbul ignore if -- 挂载点在编译完成时已经存在 */
3403
- if (!preview || !mountEl.value) return;
3404
- await (0, vue.nextTick)();
3405
- /* istanbul ignore if -- 桥接 iframe 尚未就绪或编译已被取代 */
3406
- if (pendingMount !== job || !mountEl.value) return;
3407
- pendingMount = null;
3408
- mountPreview(job, preview);
3409
- };
3410
- const run = async () => {
3411
- const current = ++version;
3412
- pendingMount = null;
3413
- compileError.value = "";
3414
- runtimeError.value = "";
3415
- publishError();
3416
- destroyPreview();
3417
- try {
3418
- const { compilePlayground } = await Promise.resolve().then(() => require("./compile-yveCpyT3.cjs"));
3419
- /* istanbul ignore if -- 并发编译时只保留最后一次 */
3420
- if (current !== version) return;
3421
- const compiled = await compilePlayground(props.files, { entry: props.entry });
3422
- /* istanbul ignore if -- 并发编译时只保留最后一次 */
3423
- if (current !== version) return;
3424
- if (compiled.errors.length || !compiled.entry) {
3425
- compileError.value = compiled.errors.join("\n") || "[Playground] 未能确定入口";
3426
- publishError();
3427
- return;
3428
- }
3429
- const imports = createRuntimeImports(props.options.cdnURL, props.options.builtinImportMap);
3430
- const overrides = new Set(Object.keys(createRuntimeImportOverrides(props.options.builtinImportMap)));
3431
- const specifiers = require_css.collectBareSpecifiers(props.files, require_css.collectReachableFilenames(props.files, compiled.entry));
3432
- specifiers.push("vue");
3433
- if (props.previewScroller === true) specifiers.push("@deot/vc");
3434
- const remote = createRemoteLoader(imports, {
3435
- vue,
3436
- vueRouter: vue_router
3437
- }, overrides, void 0, props.options.cdnURL);
3438
- const loaded = await loadLocalModules(specifiers, imports, {
3439
- vue,
3440
- vueRouter: vue_router
3441
- }, (url) => remote.load(url), overrides);
3442
- /* istanbul ignore if -- 并发编译时只保留最后一次 */
3443
- if (current !== version) return;
3444
- if (loaded.errors.length) {
3445
- compileError.value = loaded.errors.join("\n");
3446
- publishError();
3447
- return;
3448
- }
3449
- const linked = linkPlayground(compiled, props.files, { modules: loaded.modules });
3450
- /* istanbul ignore if -- 并发编译时只保留最后一次 */
3451
- if (current !== version) return;
3452
- if (linked.errors.length || !linked.component) {
3453
- compileError.value = (linked.errors.length ? linked.errors : ["[Playground] 未能解析入口组件"]).join("\n");
3454
- publishError();
3455
- return;
3456
- }
3457
- pendingMount = {
3458
- component: linked.component,
3459
- vue: loaded.modules.vue,
3460
- scroller: props.previewScroller === true ? loaded.modules["@deot/vc"].Scroller : void 0,
3461
- css: linked.css,
3462
- version: current
3463
- };
3464
- await tryMount();
3465
- } catch (error) {
3466
- /* istanbul ignore next -- 编译器自身异常才进入,普通错误走结果里的 errors */
3467
- if (current !== version) return;
3468
- /* istanbul ignore next -- 同上,只兜底编译器抛错 */
3469
- compileError.value = error instanceof Error ? error.message : String(error);
3470
- /* istanbul ignore next -- 同上,只兜底编译器抛错 */
3471
- publishError();
3472
- }
3473
- };
3474
- const handleBridgeLoad = () => {
3475
- tryMount();
3476
- };
3477
- (0, vue.onMounted)(() => {
3478
- run();
3479
- });
3480
- (0, vue.watch)(() => [
3481
- props.files,
3482
- props.entry,
3483
- props.options,
3484
- props.previewScroller
3485
- ], () => {
3486
- if (!containerEl.value) return;
3487
- run();
3488
- }, { deep: true });
3489
- (0, vue.onBeforeUnmount)(() => {
3490
- version += 1;
3491
- pendingMount = null;
3492
- destroyPreview();
3493
- });
3494
- __expose({
3495
- container: containerEl,
3496
- messageSource,
3497
- local: true,
3498
- refresh: () => {
3499
- run();
3500
- }
3501
- });
3502
- return (_ctx, _cache) => {
3503
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
3504
- ref_key: "containerEl",
3505
- ref: containerEl,
3506
- class: "docs-playground-local"
3507
- }, [(0, vue.createElementVNode)("iframe", {
3508
- ref_key: "bridgeEl",
3509
- ref: bridgeEl,
3510
- class: "docs-playground-local__bridge",
3511
- tabindex: "-1",
3512
- "aria-hidden": "true",
3513
- title: "",
3514
- srcdoc: (0, vue.unref)(bridgeSrcdoc),
3515
- onLoad: handleBridgeLoad
3516
- }, null, 40, _hoisted_1$2), (0, vue.createElementVNode)("div", {
3517
- ref_key: "mountEl",
3518
- ref: mountEl,
3519
- class: (0, vue.normalizeClass)(["docs-playground-local__mount", { "docs-playground-local__mount--auto": __props.autoHeight }])
3520
- }, null, 2)], 512);
3521
- };
3522
- }
3523
- });
3524
- //#endregion
3525
- //#region packages/playground/src/core/runtime/local/local-sandbox.vue
3526
- var local_sandbox_exports = /* @__PURE__ */ require_css.__exportAll({ default: () => local_sandbox_default });
3527
- var local_sandbox_default = local_sandbox_vue_vue_type_script_setup_true_lang_default;
3528
- //#endregion
3529
- //#region packages/playground/src/core/runtime/runtime.vue?vue&type=script&setup=true&lang.ts
3530
- var _hoisted_1$1 = {
3531
- key: 0,
3532
- class: "docs-playground__runtime-error",
3533
- role: "alert"
3534
- };
3535
- var _hoisted_2$1 = {
3536
- key: 0,
3537
- class: "docs-playground__header"
3538
- };
3539
- var _hoisted_3$1 = ["id", "title"];
3540
- var _hoisted_4$1 = ["href"];
3541
- var _hoisted_5$1 = { class: "docs-playground__title-text" };
3542
- var _hoisted_6 = {
3543
- key: 1,
3544
- class: "docs-playground__runtime-error",
3545
- role: "alert"
3546
- };
3547
- var _hoisted_7 = { class: "docs-playground-runtime__viewport-stage" };
3548
- var _hoisted_8 = [
3549
- "aria-expanded",
3550
- "title",
3551
- "aria-label"
3552
- ];
3553
- //#endregion
3554
- //#region packages/playground/src/core/runtime/runtime.vue
3555
- var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
3556
- __name: "runtime",
3557
- props: {
3558
- files: {},
3559
- entry: {},
3560
- views: { default: () => ["runtime"] },
3561
- activeView: { default: "runtime" },
3562
- options: {},
3563
- previewInset: { default: 10 },
3564
- previewOptions: {},
3565
- previewScroller: {
3566
- type: Boolean,
3567
- default: false
3568
- },
3569
- styleless: {
3570
- type: Boolean,
3571
- default: false
3572
- },
3573
- hideChrome: {
3574
- type: Boolean,
3575
- default: false
3576
- },
3577
- expandable: { type: [Boolean, Number] },
3578
- title: { default: "" },
3579
- id: { default: "" },
3580
- viewport: { default: "auto" },
3581
- viewportOptions: { default: () => ["auto", 375] },
3582
- local: {
3583
- type: Boolean,
3584
- default: true
3585
- }
3586
- },
3587
- emits: [
3588
- "files-change",
3589
- "view-change",
3590
- "viewport-change",
3591
- "navigate"
3592
- ],
3593
- setup(__props, { expose: __expose, emit: __emit }) {
3594
- const props = __props;
3595
- const { locale, t } = (0, _deot_docs_locale.useLocale)();
3596
- const displayTitle = (0, vue.computed)(() => props.title.trim());
3597
- const titleEl = (0, vue.ref)(null);
3598
- const titleId = (0, vue.computed)(() => resolvePlaygroundTitleId(displayTitle.value, props.id, (candidate) => {
3599
- if (typeof document === "undefined") return false;
3600
- const existing = document.getElementById(candidate);
3601
- return !!existing && existing !== titleEl.value;
3602
- }));
3603
- const emit = __emit;
3604
- const joinCode = (...values) => values.filter(Boolean).join("\n");
3605
- const mergePreviewOptions = (extraHeadHTML) => {
3606
- const runtimePreviewOptions = createRuntimePreviewOptions(props.options.cdnURL, { previewScroller: props.previewScroller });
3607
- return {
3608
- ...runtimePreviewOptions,
3609
- ...props.previewOptions,
3610
- headHTML: [
3611
- runtimePreviewOptions.headHTML,
3612
- props.previewOptions?.headHTML,
3613
- extraHeadHTML
3614
- ].filter(Boolean).join("\n"),
3615
- customCode: {
3616
- 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),
3617
- useCode: joinCode(runtimePreviewOptions.customCode?.useCode, props.previewOptions?.customCode?.useCode, props.previewScroller ? PREVIEW_SCROLLER_USE_CODE : void 0)
3618
- }
3619
- };
3620
- };
3621
- const mergedPreviewOptions = (0, vue.computed)(() => mergePreviewOptions());
3622
- const popupPreviewOptions = (0, vue.computed)(() => mergePreviewOptions("<style>html,body{height:100%;min-height:100%}</style>"));
3623
- const clearConsole = {
3624
- "BASE_URL": "/",
3625
- "DEV": false,
3626
- "MODE": "production",
3627
- "PROD": true,
3628
- "SSR": false
3629
- }.MODE !== "development";
3630
- const copyValue = (0, vue.computed)(() => props.files[props.entry] || "");
3631
- const store = props.local ? null : createRuntimeStore(props.files, props.entry, props.options);
3632
- const storeReady = (0, vue.computed)(() => store && !store.loading && Object.values(store.files).every((file) => require_css.isIgnoredFilename(file.filename) || !/\.(?:vue|[jt]sx?|json|s[ac]ss)$/.test(file.filename) || !file.code.trim() || !!file.compiled.js));
3633
- const sandboxRef = (0, vue.ref)(null);
3634
- const localError = (0, vue.ref)({
3635
- compile: "",
3636
- runtime: ""
3637
- });
3638
- const handleLocalError = (payload) => {
3639
- localError.value = payload;
3640
- };
3641
- const temporaryHeight = usePreviewHeightBridge(sandboxRef);
3642
- const sandboxKey = (0, vue.ref)(0);
3643
- const runtimeHeight = useSandboxAutoHeight(sandboxRef);
3644
- const runtimeError = useSandboxRuntimeErrorGuard(sandboxRef);
3645
- useSandboxTheme(sandboxRef);
3646
- const compileErrorText = (0, vue.computed)(() => {
3647
- if (props.local) return localError.value.compile;
3648
- return (store?.errors || []).map(toErrorText).filter(Boolean).join("\n");
3649
- });
3650
- const errorText = (0, vue.computed)(() => {
3651
- let runtime = "";
3652
- if (props.local) runtime = localError.value.runtime;
3653
- else if (runtimeError.value) runtime = formatSandboxRuntimeError(runtimeError.value, t("playground.runtime.importMapTip"));
3654
- return [compileErrorText.value, runtime].filter(Boolean).join("\n\n");
3655
- });
3656
- const handleBridgeMessage = (event) => {
3657
- if (!isSandboxMessage(sandboxRef.value, event)) return;
3658
- const data = event.data;
3659
- if (!data || data.action !== "docs:navigate" || typeof data.to !== "string") return;
3660
- emit("navigate", data.to);
3661
- };
3662
- const runtimeRoot = (0, vue.ref)(null);
3663
- const previewExpanded = (0, vue.ref)(false);
3664
- /** `expandable: true` 时在展开瞬间冻结,避免滚动重算高度造成抖动。 */
3665
- const frozenExpandedHeight = (0, vue.ref)(0);
3666
- const measurePreviewChromeHeight = () => {
3667
- const root = runtimeRoot.value;
3668
- if (!root) return 0;
3669
- const header = (root.closest(".docs-playground") ?? root).querySelector(".docs-playground__header");
3670
- const error = root.querySelector(".docs-playground__runtime-error");
3671
- return (header instanceof HTMLElement ? header.offsetHeight : 0) + (error instanceof HTMLElement ? error.offsetHeight : 0);
3672
- };
3673
- const normalizedPreviewInset = (0, vue.computed)(() => {
3674
- const value = props.previewInset;
3675
- if (typeof value === "number") return Number.isFinite(value) && value >= 0 ? [value, value] : [10, 10];
3676
- if (Array.isArray(value) && value.length === 2 && value.every((item) => typeof item === "number" && Number.isFinite(item) && item >= 0)) return [value[0], value[1]];
3677
- return [10, 10];
3678
- });
3679
- const measureExpandedPreviewHeight = () => {
3680
- if (typeof window === "undefined") return runtimeHeight.value;
3681
- const chromeHeight = measurePreviewChromeHeight();
3682
- const viewport = getVisibleViewportRect(runtimeRoot.value);
3683
- const [vertical] = normalizedPreviewInset.value;
3684
- return resolveExpandedPreviewHeight(true, resolveRemainingPreviewHeight({
3685
- viewportHeight: viewport.height,
3686
- chromeHeight
3687
- }) - vertical * 2 - 2);
3688
- };
3689
- const syncFrozenExpandedHeight = () => {
3690
- if (props.local || !previewExpanded.value || props.expandable !== true) return;
3691
- frozenExpandedHeight.value = measureExpandedPreviewHeight();
3692
- };
3693
- if (typeof window !== "undefined") {
3694
- window.addEventListener("message", handleBridgeMessage);
3695
- window.addEventListener("resize", syncFrozenExpandedHeight);
3696
- }
3697
- const canExpandPreview = (0, vue.computed)(() => !props.local && !props.styleless && isPlaygroundExpandable(props.expandable));
3698
- const expandLabel = (0, vue.computed)(() => t(previewExpanded.value ? "playground.runtime.collapsePreview" : "playground.runtime.expandPreview"));
3699
- const baseViewportHeight = (0, vue.computed)(() => {
3700
- if (props.local) {
3701
- const fixedHeight = getViewportHeight(props.viewport);
3702
- if (fixedHeight) return fixedHeight;
3703
- return runtimeHeight.value;
3704
- }
3705
- if (previewExpanded.value && props.expandable === true) return frozenExpandedHeight.value;
3706
- if (previewExpanded.value && isPlaygroundExpandable(props.expandable)) return resolveExpandedPreviewHeight(props.expandable, 0);
3707
- const fixedHeight = getViewportHeight(props.viewport);
3708
- if (fixedHeight) return fixedHeight;
3709
- return runtimeHeight.value;
3710
- });
3711
- const desiredViewportHeight = (0, vue.computed)(() => Math.max(baseViewportHeight.value, temporaryHeight.value));
3712
- const scrollExpandedPreviewIntoView = () => {
3713
- scrollPlaygroundToViewportStart(runtimeRoot.value);
3714
- };
3715
- const handleTogglePreviewExpand = () => {
3716
- if (props.local || !canExpandPreview.value) return;
3717
- if (!previewExpanded.value) {
3718
- if (props.expandable === true) frozenExpandedHeight.value = measureExpandedPreviewHeight();
3719
- else frozenExpandedHeight.value = 0;
3720
- previewExpanded.value = true;
3721
- (0, vue.nextTick)(scrollExpandedPreviewIntoView);
3722
- return;
3723
- }
3724
- previewExpanded.value = false;
3725
- frozenExpandedHeight.value = 0;
3726
- };
3727
- (0, vue.watch)(canExpandPreview, (enabled) => {
3728
- if (!enabled) {
3729
- previewExpanded.value = false;
3730
- frozenExpandedHeight.value = 0;
3731
- }
3732
- });
3733
- const previewStyle = (0, vue.computed)(() => {
3734
- const [vertical, horizontal] = normalizedPreviewInset.value;
3735
- return {
3736
- height: `${resolvePreviewBoxHeight(desiredViewportHeight.value, vertical)}px`,
3737
- padding: vertical === horizontal ? `${vertical}px` : `${vertical}px ${horizontal}px`,
3738
- background: PLAYGROUND_RUNTIME_CANVAS_BACKGROUND
3739
- };
3740
- });
3741
- const stylelessStyle = (0, vue.computed)(() => errorText.value ? void 0 : { height: `${desiredViewportHeight.value}px` });
3742
- const viewportStyle = (0, vue.computed)(() => {
3743
- const width = getViewportWidth(props.viewport);
3744
- return {
3745
- width: width ? `${width}px` : "100%",
3746
- maxWidth: "100%",
3747
- height: "100%"
3748
- };
3749
- });
3750
- let syncedFiles = { ...props.files };
3751
- let syncedEntry = props.entry;
3752
- const handleFilesChange = (files, entry, action) => {
3753
- syncedFiles = { ...files };
3754
- syncedEntry = entry;
3755
- const apply = () => {
3756
- if (store) switch (action.type) {
3757
- case "update": {
3758
- const file = store.files[toReplFilename(action.filename)];
3759
- if (file) file.code = files[action.filename];
3760
- break;
3761
- }
3762
- case "create":
3763
- store.addFile(createReplFile(action.filename, files[action.filename]));
3764
- break;
3765
- case "rename":
3766
- store.renameFile(toReplFilename(action.previousFilename), toReplFilename(action.filename));
3767
- break;
3768
- case "delete":
3769
- store.setActive(toReplFilename(entry));
3770
- delete store.files[toReplFilename(action.filename)];
3771
- break;
3772
- case "entry":
3773
- store.mainFile = toReplFilename(entry);
3774
- store.setActive(toReplFilename(entry));
3775
- }
3776
- emit("files-change", files, entry, action);
3777
- };
3778
- if (store) require_css.whenSassReady(files, apply);
3779
- else apply();
3780
- };
3781
- const handleEditor = () => {
3782
- Editor.popup({
3783
- files: { ...props.files },
3784
- entry: props.entry,
3785
- locale: locale.value,
3786
- getCodeErrors: () => {
3787
- if (!props.local) return store?.errors || [];
3788
- return localError.value.compile ? [localError.value.compile] : [];
3789
- },
3790
- onFilesChange: handleFilesChange,
3791
- onActiveChange: (filename) => store?.setActive(toReplFilename(filename))
3792
- });
3793
- };
3794
- const handleInlineRefresh = () => {
3795
- runtimeError.value = "";
3796
- localError.value = {
3797
- compile: "",
3798
- runtime: ""
3799
- };
3800
- sandboxKey.value++;
3801
- };
3802
- const handleClosePopup = () => {
3803
- Alone.destroy();
3804
- };
3805
- const handleOpenPopup = () => {
3806
- Alone.popup({
3807
- local: props.local,
3808
- files: props.files,
3809
- entry: props.entry,
3810
- options: props.options,
3811
- previewScroller: props.previewScroller,
3812
- store,
3813
- copyValue: copyValue.value,
3814
- title: props.title,
3815
- viewport: props.viewport,
3816
- viewportOptions: props.viewportOptions,
3817
- previewOptions: popupPreviewOptions.value,
3818
- clearConsole,
3819
- onEdit: handleEditor,
3820
- onViewportChange: handleViewportChange,
3821
- onNavigate: (to) => emit("navigate", to)
3822
- });
3823
- };
3824
- const handleViewportChange = (viewport) => {
3825
- emit("viewport-change", viewport);
3826
- };
3827
- (0, vue.watch)(() => props.files, (files) => {
3828
- if (filesEqual(files, syncedFiles)) return;
3829
- syncedFiles = { ...files };
3830
- syncedEntry = props.entry;
3831
- runtimeError.value = "";
3832
- localError.value = {
3833
- compile: "",
3834
- runtime: ""
3835
- };
3836
- if (store) store.setFiles(files, props.entry);
3837
- }, { deep: true });
3838
- (0, vue.watch)(() => props.entry, (entry) => {
3839
- if (!entry || entry === syncedEntry) return;
3840
- syncedEntry = entry;
3841
- if (!store) return;
3842
- store.mainFile = toReplFilename(entry);
3843
- store.setActive(toReplFilename(entry));
3844
- });
3845
- (0, vue.watch)(() => props.activeView, (view) => {
3846
- if (view !== "runtime") handleClosePopup();
3847
- });
3848
- (0, vue.onBeforeUnmount)(() => {
3849
- window.removeEventListener("message", handleBridgeMessage);
3850
- window.removeEventListener("resize", syncFrozenExpandedHeight);
3851
- handleClosePopup();
3852
- });
3853
- __expose({
3854
- refresh: handleInlineRefresh,
3855
- edit: handleEditor,
3856
- openPopup: handleOpenPopup
3857
- });
3858
- return (_ctx, _cache) => {
3859
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", { class: (0, vue.normalizeClass)(["docs-playground-runtime-host", { "docs-playground-runtime-host--direct": __props.local && __props.styleless }]) }, [__props.styleless ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
3860
- key: 0,
3861
- ref_key: "runtimeRoot",
3862
- ref: runtimeRoot,
3863
- class: "docs-playground-runtime--styleless",
3864
- style: (0, vue.normalizeStyle)(stylelessStyle.value)
3865
- }, [errorText.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("pre", _hoisted_1$1, (0, vue.toDisplayString)(errorText.value), 1)) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createElementVNode)("div", {
3866
- class: "docs-playground-runtime__viewport",
3867
- style: (0, vue.normalizeStyle)(viewportStyle.value)
3868
- }, [__props.local ? ((0, vue.openBlock)(), (0, vue.createBlock)(local_sandbox_default, {
3869
- key: 0,
3870
- ref_key: "sandboxRef",
3871
- ref: sandboxRef,
3872
- files: __props.files,
3873
- entry: __props.entry,
3874
- options: __props.options,
3875
- "preview-scroller": __props.previewScroller,
3876
- "clear-console": clearConsole,
3877
- "auto-height": !(0, vue.unref)(getViewportHeight)(__props.viewport),
3878
- onError: handleLocalError
3879
- }, null, 8, [
3880
- "files",
3881
- "entry",
3882
- "options",
3883
- "preview-scroller",
3884
- "auto-height"
3885
- ])) : (0, vue.unref)(store) && storeReady.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_vue_repl.Sandbox), {
3886
- key: 1,
3887
- ref_key: "sandboxRef",
3888
- ref: sandboxRef,
3889
- store: (0, vue.unref)(store),
3890
- "auto-store-init": false,
3891
- "preview-options": mergedPreviewOptions.value
3892
- }, null, 8, ["store", "preview-options"])) : (0, vue.createCommentVNode)("v-if", true)], 4)], 4)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
3893
- key: 1,
3894
- ref_key: "runtimeRoot",
3895
- ref: runtimeRoot,
3896
- class: "docs-playground-runtime"
3897
- }, [
3898
- !__props.hideChrome ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$1, [displayTitle.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
3899
- key: 0,
3900
- ref_key: "titleEl",
3901
- ref: titleEl,
3902
- class: "docs-playground__title",
3903
- id: titleId.value,
3904
- tabindex: "-1",
3905
- title: displayTitle.value
3906
- }, [(0, vue.createElementVNode)("a", {
3907
- class: "docs-playground__title-anchor",
3908
- href: `#${titleId.value}`
3909
- }, "#", 8, _hoisted_4$1), (0, vue.createElementVNode)("span", _hoisted_5$1, (0, vue.toDisplayString)(displayTitle.value), 1)], 8, _hoisted_3$1)) : (0, vue.createCommentVNode)("v-if", true), (0, vue.createVNode)(toolbar_default, {
3910
- "copy-value": copyValue.value,
3911
- viewport: __props.viewport,
3912
- "viewport-options": __props.viewportOptions,
3913
- "show-open-popup": true,
3914
- onRefresh: handleInlineRefresh,
3915
- onEdit: handleEditor,
3916
- onOpenPopup: handleOpenPopup,
3917
- onViewportChange: handleViewportChange
3918
- }, null, 8, [
3919
- "copy-value",
3920
- "viewport",
3921
- "viewport-options"
3922
- ])])) : (0, vue.createCommentVNode)("v-if", true),
3923
- errorText.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("pre", _hoisted_6, (0, vue.toDisplayString)(errorText.value), 1)) : (0, vue.createCommentVNode)("v-if", true),
3924
- (0, vue.createElementVNode)("section", {
3925
- class: (0, vue.normalizeClass)(["docs-playground__preview", { "is-expanded": previewExpanded.value }]),
3926
- style: (0, vue.normalizeStyle)(previewStyle.value)
3927
- }, [(0, vue.createElementVNode)("div", _hoisted_7, [(0, vue.createElementVNode)("div", {
3928
- class: "docs-playground-runtime__viewport",
3929
- style: (0, vue.normalizeStyle)(viewportStyle.value)
3930
- }, [__props.local ? ((0, vue.openBlock)(), (0, vue.createBlock)(local_sandbox_default, {
3931
- key: sandboxKey.value,
3932
- ref_key: "sandboxRef",
3933
- ref: sandboxRef,
3934
- files: __props.files,
3935
- entry: __props.entry,
3936
- options: __props.options,
3937
- "preview-scroller": __props.previewScroller,
3938
- "clear-console": clearConsole,
3939
- "auto-height": !(0, vue.unref)(getViewportHeight)(__props.viewport),
3940
- onError: handleLocalError
3941
- }, null, 8, [
3942
- "files",
3943
- "entry",
3944
- "options",
3945
- "preview-scroller",
3946
- "auto-height"
3947
- ])) : (0, vue.unref)(store) && storeReady.value ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_vue_repl.Sandbox), {
3948
- key: sandboxKey.value,
3949
- ref_key: "sandboxRef",
3950
- ref: sandboxRef,
3951
- store: (0, vue.unref)(store),
3952
- "auto-store-init": false,
3953
- "clear-console": clearConsole,
3954
- "preview-options": mergedPreviewOptions.value
3955
- }, null, 8, ["store", "preview-options"])) : (0, vue.createCommentVNode)("v-if", true)], 4)]), canExpandPreview.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
3956
- key: 0,
3957
- type: "button",
3958
- class: (0, vue.normalizeClass)(["docs-playground__expand", { "is-expanded": previewExpanded.value }]),
3959
- "data-action": "expand-preview",
3960
- "aria-expanded": previewExpanded.value,
3961
- title: expandLabel.value,
3962
- "aria-label": expandLabel.value,
3963
- onClick: handleTogglePreviewExpand
3964
- }, [(0, vue.createVNode)((0, vue.unref)(icon_default), { name: "expand" })], 10, _hoisted_8)) : (0, vue.createCommentVNode)("v-if", true)], 6)
3965
- ], 512))], 2);
3966
- };
3967
- }
3968
- });
3969
- //#endregion
3970
- //#region packages/playground/src/playground.vue?vue&type=script&setup=true&lang.ts
3971
- var _hoisted_1 = {
3972
- key: 0,
3973
- class: "docs-playground__error"
3974
- };
3975
- var _hoisted_2 = {
3976
- key: 0,
3977
- class: "docs-playground__header"
3978
- };
3979
- var _hoisted_3 = ["id", "title"];
3980
- var _hoisted_4 = ["href"];
3981
- var _hoisted_5 = { class: "docs-playground__title-text" };
3982
- //#endregion
3983
- //#region packages/playground/src/playground.vue
3984
- var playground_default = /* @__PURE__ */ (0, vue.defineComponent)({
3985
- __name: "playground",
3986
- props: {
3987
- modelValue: { default: "" },
3988
- files: { default: () => ({}) },
3989
- entry: { default: "" },
3990
- views: { default: () => ["runtime"] },
3991
- viewport: {},
3992
- viewportOptions: {},
3993
- styleless: {
3994
- type: Boolean,
3995
- default: false
3996
- },
3997
- options: { default: () => ({}) },
3998
- previewInset: { default: 0 },
3999
- expandable: { type: [Boolean, Number] },
4000
- title: { default: "" },
4001
- id: { default: "" },
4002
- previewScroller: {
4003
- type: Boolean,
4004
- default: false
4005
- },
4006
- local: {
4007
- type: Boolean,
4008
- default: true
4009
- },
4010
- previewOptions: {},
4011
- locale: {}
4012
- },
4013
- emits: [
4014
- "update:modelValue",
4015
- "update:files",
4016
- "update:entry",
4017
- "update:viewport",
4018
- "change",
4019
- "navigate"
4020
- ],
4021
- setup(__props, { emit: __emit }) {
4022
- const emit = __emit;
4023
- const props = __props;
4024
- const inheritedLocale = (0, _deot_docs_locale.useLocale)();
4025
- const locale = (0, vue.computed)(() => props.locale || inheritedLocale.locale.value);
4026
- (0, _deot_docs_locale.provideLocale)(locale);
4027
- const { t } = (0, _deot_docs_locale.useLocale)(locale);
4028
- const initialFiles = Object.keys(props.files).length ? { ...props.files } : { [require_css.DEFAULT_ENTRY]: props.modelValue || require_css.NEW_SFC_CODE };
4029
- const initialEntry = props.entry && initialFiles[props.entry] !== void 0 ? props.entry : Object.keys(initialFiles)[0];
4030
- const sourceFiles = (0, vue.ref)(initialFiles);
4031
- const currentEntry = (0, vue.ref)(initialEntry);
4032
- const fileActiveFilename = (0, vue.ref)(initialEntry);
4033
- const runtimeRef = (0, vue.ref)(null);
4034
- const titleEl = (0, vue.ref)(null);
4035
- const normalizeViews = (views) => {
4036
- const normalized = [];
4037
- for (const view of views) {
4038
- if (view !== "runtime" && view !== "files") continue;
4039
- if (normalized.includes(view)) continue;
4040
- normalized.push(view);
4041
- }
4042
- return normalized.length ? normalized : ["runtime"];
4043
- };
4044
- const normalizedViews = (0, vue.computed)(() => normalizeViews(props.views));
4045
- const hasRuntimeView = (0, vue.computed)(() => normalizedViews.value.includes("runtime"));
4046
- const hasFilesView = (0, vue.computed)(() => normalizedViews.value.includes("files"));
4047
- const activeView = (0, vue.ref)(normalizedViews.value[0]);
4048
- const runtimeActivated = (0, vue.ref)(props.styleless || activeView.value === "runtime");
4049
- const normalizedViewportOptions = (0, vue.computed)(() => normalizeViewportOptions(props.viewportOptions));
4050
- const activeViewport = (0, vue.ref)(resolveInitialViewport(props.viewport, normalizedViewportOptions.value));
4051
- const selectableViewportOptions = (0, vue.computed)(() => includeActiveViewport(normalizedViewportOptions.value, activeViewport.value));
4052
- const error = (0, vue.ref)(props.entry && initialFiles[props.entry] === void 0 ? t("playground.validation.entryMissing", { filename: props.entry }) : "");
4053
- const displayTitle = (0, vue.computed)(() => props.title.trim());
4054
- const titleId = (0, vue.computed)(() => resolvePlaygroundTitleId(displayTitle.value, props.id, (candidate) => {
4055
- if (typeof document === "undefined") return false;
4056
- const existing = document.getElementById(candidate);
4057
- return !!existing && existing !== titleEl.value;
4058
- }));
4059
- const copyValue = (0, vue.computed)(() => sourceFiles.value[currentEntry.value] || "");
4060
- /** runtime / 双视图始终有顶栏;files-only 仅有标题时渲染。 */
4061
- const showHeader = (0, vue.computed)(() => hasRuntimeView.value || !!displayTitle.value);
4062
- const showToolbar = (0, vue.computed)(() => hasRuntimeView.value);
4063
- const showRuntimeActions = (0, vue.computed)(() => activeView.value === "runtime");
4064
- (0, vue.watch)(locale, () => {
4065
- if (props.entry && sourceFiles.value[props.entry] === void 0) error.value = t("playground.validation.entryMissing", { filename: props.entry });
4066
- });
4067
- const emitFiles = () => emit("update:files", { ...sourceFiles.value });
4068
- const handleView = (view) => {
4069
- activeView.value = view;
4070
- if (view === "runtime") runtimeActivated.value = true;
4071
- };
4072
- const handleViewport = (viewport) => {
4073
- if (!isValidViewport(viewport) || viewportEquals(activeViewport.value, viewport)) return;
4074
- activeViewport.value = cloneViewport(viewport);
4075
- emit("update:viewport", cloneViewport(viewport));
4076
- };
4077
- const handleRefresh = () => {
4078
- runtimeRef.value?.refresh();
4079
- };
4080
- const handleEdit = () => {
4081
- runtimeRef.value?.edit();
4082
- };
4083
- const handleOpenPopup = () => {
4084
- runtimeRef.value?.openPopup();
4085
- };
4086
- const handleFileActive = (filename) => {
4087
- if (sourceFiles.value[filename] !== void 0) fileActiveFilename.value = filename;
4088
- };
4089
- const handleFilesChange = (files, entry, action) => {
4090
- const previousEntry = currentEntry.value;
4091
- sourceFiles.value = { ...files };
4092
- currentEntry.value = entry;
4093
- error.value = "";
4094
- switch (action.type) {
4095
- case "create":
4096
- fileActiveFilename.value = action.filename;
4097
- break;
4098
- case "rename":
4099
- if (fileActiveFilename.value === action.previousFilename) fileActiveFilename.value = action.filename;
4100
- break;
4101
- case "delete":
4102
- if (fileActiveFilename.value === action.filename) fileActiveFilename.value = entry;
4103
- break;
4104
- case "entry": fileActiveFilename.value = entry;
4105
- }
4106
- if (action.type !== "entry") emitFiles();
4107
- if (entry !== previousEntry) emit("update:entry", entry);
4108
- if (action.type === "update" && action.filename === entry) {
4109
- emit("update:modelValue", files[entry]);
4110
- emit("change", files[entry]);
4111
- }
4112
- };
4113
- (0, vue.watch)(() => props.modelValue, (value) => {
4114
- if (Object.keys(props.files).length || sourceFiles.value[currentEntry.value] === value) return;
4115
- sourceFiles.value = {
4116
- ...sourceFiles.value,
4117
- [currentEntry.value]: value
4118
- };
4119
- });
4120
- (0, vue.watch)(() => props.files, (files) => {
4121
- if (!Object.keys(files).length || filesEqual(files, sourceFiles.value)) return;
4122
- const previousEntry = currentEntry.value;
4123
- const entry = props.entry && files[props.entry] !== void 0 ? props.entry : Object.keys(files)[0];
4124
- sourceFiles.value = { ...files };
4125
- currentEntry.value = entry;
4126
- error.value = props.entry && files[props.entry] === void 0 ? t("playground.validation.entryMissing", { filename: props.entry }) : "";
4127
- if (files[fileActiveFilename.value] === void 0 || fileActiveFilename.value === previousEntry) fileActiveFilename.value = entry;
4128
- }, { deep: true });
4129
- (0, vue.watch)(() => props.entry, (entry) => {
4130
- if (!entry || entry === currentEntry.value || !Object.keys(props.files).length) return;
4131
- if (sourceFiles.value[entry] === void 0) {
4132
- error.value = t("playground.validation.entryMissing", { filename: entry });
4133
- return;
4134
- }
4135
- const previousEntry = currentEntry.value;
4136
- currentEntry.value = entry;
4137
- if (fileActiveFilename.value === previousEntry) fileActiveFilename.value = entry;
4138
- error.value = "";
4139
- });
4140
- (0, vue.watch)(normalizedViews, (views) => {
4141
- if (!views.includes(activeView.value)) activeView.value = views[0];
4142
- if (!views.includes("runtime")) runtimeActivated.value = false;
4143
- else if (activeView.value === "runtime") runtimeActivated.value = true;
4144
- });
4145
- (0, vue.watch)(() => props.viewport, (viewport) => {
4146
- const nextViewport = resolveInitialViewport(viewport, normalizedViewportOptions.value);
4147
- if (viewportEquals(activeViewport.value, nextViewport)) return;
4148
- activeViewport.value = nextViewport;
4149
- });
4150
- (0, vue.watch)(normalizedViewportOptions, (options) => {
4151
- if (!options.length) {
4152
- const nextViewport = resolveInitialViewport(props.viewport, options);
4153
- if (!viewportEquals(activeViewport.value, nextViewport)) activeViewport.value = nextViewport;
4154
- return;
4155
- }
4156
- if (options.some((viewport) => viewportEquals(viewport, activeViewport.value))) return;
4157
- activeViewport.value = isValidViewport(props.viewport) ? cloneViewport(props.viewport) : cloneViewport(options[0]);
4158
- });
4159
- return (_ctx, _cache) => {
4160
- return __props.styleless ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(runtime_default), {
4161
- key: 0,
4162
- files: sourceFiles.value,
4163
- entry: currentEntry.value,
4164
- options: __props.options,
4165
- "preview-options": __props.previewOptions,
4166
- "preview-inset": __props.previewInset,
4167
- "preview-scroller": __props.previewScroller,
4168
- local: __props.local,
4169
- expandable: __props.local ? false : __props.expandable,
4170
- styleless: true,
4171
- title: __props.title,
4172
- id: __props.id,
4173
- viewport: activeViewport.value,
4174
- "viewport-options": selectableViewportOptions.value,
4175
- onFilesChange: handleFilesChange,
4176
- onNavigate: _cache[0] || (_cache[0] = ($event) => emit("navigate", $event)),
4177
- onViewportChange: handleViewport
4178
- }, null, 8, [
4179
- "files",
4180
- "entry",
4181
- "options",
4182
- "preview-options",
4183
- "preview-inset",
4184
- "preview-scroller",
4185
- "local",
4186
- "expandable",
4187
- "title",
4188
- "id",
4189
- "viewport",
4190
- "viewport-options"
4191
- ])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
4192
- key: 1,
4193
- class: (0, vue.normalizeClass)(["docs-playground", { "docs-playground--files": activeView.value === "files" }])
4194
- }, [error.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1, (0, vue.toDisplayString)(error.value), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 1 }, [
4195
- showHeader.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2, [displayTitle.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
4196
- key: 0,
4197
- ref_key: "titleEl",
4198
- ref: titleEl,
4199
- class: "docs-playground__title",
4200
- id: titleId.value,
4201
- tabindex: "-1",
4202
- title: displayTitle.value
4203
- }, [(0, vue.createElementVNode)("a", {
4204
- class: "docs-playground__title-anchor",
4205
- href: `#${titleId.value}`
4206
- }, "#", 8, _hoisted_4), (0, vue.createElementVNode)("span", _hoisted_5, (0, vue.toDisplayString)(displayTitle.value), 1)], 8, _hoisted_3)) : (0, vue.createCommentVNode)("v-if", true), showToolbar.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(toolbar_default, {
4207
- key: 1,
4208
- "copy-value": copyValue.value,
4209
- viewport: activeViewport.value,
4210
- "viewport-options": selectableViewportOptions.value,
4211
- views: normalizedViews.value,
4212
- "active-view": activeView.value,
4213
- "show-runtime-actions": showRuntimeActions.value,
4214
- "show-open-popup": hasRuntimeView.value,
4215
- onRefresh: handleRefresh,
4216
- onEdit: handleEdit,
4217
- onOpenPopup: handleOpenPopup,
4218
- onViewportChange: handleViewport,
4219
- onViewChange: handleView
4220
- }, null, 8, [
4221
- "copy-value",
4222
- "viewport",
4223
- "viewport-options",
4224
- "views",
4225
- "active-view",
4226
- "show-runtime-actions",
4227
- "show-open-popup"
4228
- ])) : (0, vue.createCommentVNode)("v-if", true)])) : (0, vue.createCommentVNode)("v-if", true),
4229
- runtimeActivated.value && hasRuntimeView.value ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(runtime_default), {
4230
- key: 1,
4231
- ref_key: "runtimeRef",
4232
- ref: runtimeRef,
4233
- files: sourceFiles.value,
4234
- entry: currentEntry.value,
4235
- options: __props.options,
4236
- "preview-options": __props.previewOptions,
4237
- "preview-inset": __props.previewInset,
4238
- "preview-scroller": __props.previewScroller,
4239
- local: __props.local,
4240
- expandable: __props.local ? false : __props.expandable,
4241
- "hide-chrome": true,
4242
- title: __props.title,
4243
- id: __props.id,
4244
- "active-view": activeView.value,
4245
- views: normalizedViews.value,
4246
- viewport: activeViewport.value,
4247
- "viewport-options": selectableViewportOptions.value,
4248
- onFilesChange: handleFilesChange,
4249
- onNavigate: _cache[1] || (_cache[1] = ($event) => emit("navigate", $event)),
4250
- onViewportChange: handleViewport
4251
- }, null, 8, [
4252
- "files",
4253
- "entry",
4254
- "options",
4255
- "preview-options",
4256
- "preview-inset",
4257
- "preview-scroller",
4258
- "local",
4259
- "expandable",
4260
- "title",
4261
- "id",
4262
- "active-view",
4263
- "views",
4264
- "viewport",
4265
- "viewport-options"
4266
- ])), [[vue.vShow, activeView.value === "runtime"]]) : (0, vue.createCommentVNode)("v-if", true),
4267
- hasFilesView.value ? (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(files_preview_default), {
4268
- key: 2,
4269
- files: sourceFiles.value,
4270
- entry: currentEntry.value,
4271
- "active-filename": fileActiveFilename.value,
4272
- "active-view": activeView.value,
4273
- views: normalizedViews.value,
4274
- onActiveChange: handleFileActive
4275
- }, null, 8, [
4276
- "files",
4277
- "entry",
4278
- "active-filename",
4279
- "active-view",
4280
- "views"
4281
- ])), [[vue.vShow, activeView.value === "files"]]) : (0, vue.createCommentVNode)("v-if", true)
4282
- ], 64))], 2));
4283
- };
4284
- }
4285
- });
4286
- //#endregion
4287
- exports.CodePreview = code_preview_default;
4288
- exports.DEFAULT_CDN_URL = require_css.DEFAULT_CDN_URL;
4289
- exports.Editor = Editor;
4290
- exports.PREVIEW_SCROLLER_USE_CODE = PREVIEW_SCROLLER_USE_CODE;
4291
- exports.PREVIEW_SCROLL_CONTENT_CLASS = PREVIEW_SCROLL_CONTENT_CLASS;
4292
- exports.PREVIEW_SCROLL_HTML_CLASS = PREVIEW_SCROLL_HTML_CLASS;
4293
- exports.Playground = playground_default;
4294
- exports.PlaygroundService = PlaygroundService;
4295
- exports.applyPlaygroundImportMapOverride = applyPlaygroundImportMapOverride;
4296
- exports.applyPlaygroundStyleOverride = applyPlaygroundStyleOverride;
4297
- exports.clearPlaygroundMaps = clearPlaygroundMaps;
4298
- exports.createBuiltinImports = require_css.createBuiltinImports;
4299
- exports.createBuiltinStyles = require_css.createBuiltinStyles;
4300
- exports.createRuntimePreviewOptions = createRuntimePreviewOptions;
4301
- exports.getPlaygroundImportMapOverrides = getPlaygroundImportMapOverrides;
4302
- exports.getPlaygroundSiteModules = getPlaygroundSiteModules;
4303
- exports.getPlaygroundSiteStyles = getPlaygroundSiteStyles;
4304
- exports.getPlaygroundStyleOverrides = getPlaygroundStyleOverrides;
4305
- exports.highlightCode = highlightCode;
4306
- exports.highlightCodeByLanguage = highlightCodeByLanguage;
4307
- exports.isSafePlaygroundHref = isSafePlaygroundHref;
4308
- exports.normalizeCdnURL = require_css.normalizeCdnURL;
4309
- exports.registerVueHighlight = registerVueHighlight;
4310
- exports.removePlaygroundImportMapOverride = removePlaygroundImportMapOverride;
4311
- exports.removePlaygroundStyleOverride = removePlaygroundStyleOverride;
4312
- exports.resolveCdnURL = require_css.resolveCdnURL;
4313
- exports.resolveHighlightLanguage = resolveHighlightLanguage;
4314
- exports.resolvePlaygroundTitleId = resolvePlaygroundTitleId;
4315
- exports.setPlaygroundImportMapOverrides = setPlaygroundImportMapOverrides;
4316
- exports.setPlaygroundSiteModules = setPlaygroundSiteModules;
4317
- exports.setPlaygroundSiteStyles = setPlaygroundSiteStyles;
4318
- exports.setPlaygroundStyleOverrides = setPlaygroundStyleOverrides;
4319
- exports.slugifyPlaygroundTitle = slugifyPlaygroundTitle;
4320
- exports.vueHighlight = vueHighlight;