@deot/docs-playground 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -177,10 +177,10 @@ var icon_default = /* @__PURE__ */ (0, vue.defineComponent)({
177
177
  });
178
178
  //#endregion
179
179
  //#region packages/playground/src/core/code-preview/github.css?inline
180
- var github_default = "pre code.hljs{display:block;padding:1em;overflow-x:auto}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}[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}";
180
+ 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}[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}";
181
181
  //#endregion
182
182
  //#region packages/playground/src/core/code-preview/code-preview.scss?inline
183
- 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 ease;transition:opacity .15s ease}.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-color-light-deeper,#d4d4d8));border-radius:8px;opacity:0;-webkit-transition:opacity .15s ease,background-color .15s ease;transition:opacity .15s ease,background-color .15s ease}.docs-code-preview__copy:hover{background:var(--docs-background-color,var(--vc-background-color-light,#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__code{width:100%;min-height:0;margin:0;overflow:auto;-webkit-box-flex:1;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto}.docs-code-preview__code,.docs-code-preview__code code.hljs{background:var(--docs-background-color-soft,var(--vc-background-color,#f7f8fa))}.docs-code-preview__code code.hljs{min-height:100%;padding:12px 96px 12px 24px;-webkit-box-sizing:border-box;box-sizing:border-box}";
183
+ 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 ease;transition:opacity .15s ease}.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-color-light-deeper,#d4d4d8));border-radius:8px;opacity:0;-webkit-transition:opacity .15s ease,background-color .15s ease;transition:opacity .15s ease,background-color .15s ease}.docs-code-preview__copy:hover{background:var(--docs-background-color,var(--vc-background-color-light,#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}";
184
184
  //#endregion
185
185
  //#region packages/playground/src/core/code-preview/style.ts
186
186
  var CODE_PREVIEW_STYLE_ID = "docs-code-preview-style";
@@ -252,11 +252,19 @@ var code_preview_default = /* @__PURE__ */ (0, vue.defineComponent)({
252
252
  "title",
253
253
  "aria-label"
254
254
  ]),
255
- (0, vue.createCommentVNode)(" eslint-disable-next-line vue/no-v-html "),
256
- (0, vue.createElementVNode)("pre", _hoisted_3$6, [(0, vue.createElementVNode)("code", {
257
- class: "hljs",
258
- innerHTML: highlightedCode.value
259
- }, null, 8, _hoisted_4$6)])
255
+ (0, vue.createVNode)((0, vue.unref)(_deot_vc.Scroller), {
256
+ class: "docs-code-preview__scroller",
257
+ "auto-resize": true,
258
+ native: false,
259
+ "show-bar": true,
260
+ height: "100%"
261
+ }, {
262
+ 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", {
263
+ class: "hljs",
264
+ innerHTML: highlightedCode.value
265
+ }, null, 8, _hoisted_4$6)])]),
266
+ _: 1
267
+ })
260
268
  ]);
261
269
  };
262
270
  }
@@ -852,19 +860,120 @@ var resolvePlaygroundTitleId = (title, explicitId = "", isOccupied) => {
852
860
  while (isOccupied(`${base}-${index}`)) index += 1;
853
861
  return `${base}-${index}`;
854
862
  };
863
+ //#endregion
864
+ //#region packages/playground/src/core/preview-scroll.ts
865
+ /** iframe 包装成功后挂在 `html` 上,供样式与 auto-height 识别。 */
866
+ var PREVIEW_SCROLL_HTML_CLASS = "docs-playground-preview-scroll";
867
+ /** Scroller 内容节点 class,auto-height 优先测量该节点。 */
868
+ var PREVIEW_SCROLL_CONTENT_CLASS = "docs-playground-preview-scroll__content";
869
+ /** 重评时先清掉 html class,避免 `await import` 期间仍套用 `height:100%`。 */
870
+ var PREVIEW_SCROLL_RESET_CODE = `document.documentElement.classList.remove("${PREVIEW_SCROLL_HTML_CLASS}");`;
871
+ /** 动态探测 `@deot/vc`;失败时为空对象,不阻断 DocsLink / 预览挂载。 */
872
+ var PREVIEW_SCROLLER_IMPORT_CODE = "const __docsVc=await import(\"@deot/vc\").catch(()=>({}))";
873
+ /**
874
+ * Vue 的 `mount` 闭包的是 `createApp(root)` 时的根组件,改 `app._component` 不会生效。
875
+ * 因此劫持 `app.mount`:等全部 useCode 注册完后,再 `createApp` 包一层 Scroller。
876
+ * 前导分号避免接在 `app.component(...)` 后被 ASI 解析成调用返回值。
877
+ */
878
+ var PREVIEW_SCROLLER_USE_CODE = [
879
+ ";(()=>{",
880
+ PREVIEW_SCROLL_RESET_CODE,
881
+ "const __DocsScroller=__docsVc&&__docsVc.Scroller;",
882
+ "if(!__DocsScroller)return;",
883
+ "const UserApp=app._component;",
884
+ "app.mount=(container,...rest)=>{",
885
+ "const next=_createApp({",
886
+ "name:\"DocsPreviewRoot\",",
887
+ "setup(){return()=>__docsH(__DocsScroller,{",
888
+ "height:\"100%\",",
889
+ "native:false,",
890
+ "autoResize:true,",
891
+ "showBar:true,",
892
+ `contentClass:"${PREVIEW_SCROLL_CONTENT_CLASS}"`,
893
+ "},{default:()=>__docsH(UserApp)})}",
894
+ "});",
895
+ "next.config.errorHandler=app.config.errorHandler;",
896
+ "if(app._context&&next._context){",
897
+ "if(app._context.components)Object.assign(next._context.components,app._context.components);",
898
+ "if(app._context.directives)Object.assign(next._context.directives,app._context.directives);",
899
+ "Object.assign(next._context.provides,app._context.provides);",
900
+ "next._context.mixins.push(...app._context.mixins);",
901
+ "}",
902
+ "window.__app__=next;",
903
+ `document.documentElement.classList.add("${PREVIEW_SCROLL_HTML_CLASS}");`,
904
+ "return next.mount(container,...rest)",
905
+ "}",
906
+ "})()"
907
+ ].join("");
908
+ var PREVIEW_SCROLLER_STYLE = [
909
+ `html.${PREVIEW_SCROLL_HTML_CLASS},`,
910
+ `html.${PREVIEW_SCROLL_HTML_CLASS} body,`,
911
+ `html.${PREVIEW_SCROLL_HTML_CLASS} #app{`,
912
+ "height:100%;min-height:100%;margin:0;overflow:hidden",
913
+ "}",
914
+ `html.${PREVIEW_SCROLL_HTML_CLASS} #app>.vc-scroller,`,
915
+ `html.${PREVIEW_SCROLL_HTML_CLASS} #app>.vc-scroller>.vc-scroller__wrapper{`,
916
+ "height:100%;max-height:100%;box-sizing:border-box",
917
+ "}"
918
+ ].join("");
855
919
  var resolveSandboxContainer = (sandbox) => sandbox?.container ? (0, vue.unref)(sandbox.container) : null;
920
+ var resolveScrollContent = (iframeDocument) => {
921
+ if (!iframeDocument.documentElement.classList.contains("docs-playground-preview-scroll")) return null;
922
+ return iframeDocument.querySelector(`.${PREVIEW_SCROLL_CONTENT_CLASS}`);
923
+ };
856
924
  var useSandboxAutoHeight = (sandboxRef) => {
857
925
  const height = (0, vue.ref)(24);
858
926
  let container = null;
859
927
  let iframe = null;
860
928
  let containerObserver = null;
861
929
  let contentObserver = null;
930
+ let scrollMountObserver = null;
931
+ let observedScrollNode = null;
862
932
  let frameId = 0;
863
933
  const cancelMeasure = () => {
864
934
  if (!frameId) return;
865
935
  cancelAnimationFrame(frameId);
866
936
  frameId = 0;
867
937
  };
938
+ const measureDocumentContent = (iframeWindow, iframeDocument) => {
939
+ const { body, documentElement } = iframeDocument;
940
+ const bodyStyle = iframeWindow.getComputedStyle(body);
941
+ const bodyMargins = (Number.parseFloat(bodyStyle.marginTop) || 0) + (Number.parseFloat(bodyStyle.marginBottom) || 0);
942
+ const rootTop = documentElement.getBoundingClientRect().top;
943
+ const bodyRect = body.getBoundingClientRect();
944
+ const collapsedTop = Math.max(0, bodyRect.top - rootTop);
945
+ let childrenBottom = 0;
946
+ for (const child of body.children) {
947
+ if (iframeWindow.getComputedStyle(child).position === "fixed") continue;
948
+ const rect = child.getBoundingClientRect();
949
+ const marginBottom = Number.parseFloat(iframeWindow.getComputedStyle(child).marginBottom) || 0;
950
+ childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
951
+ }
952
+ const scrollHeight = Math.max(body.scrollHeight, documentElement.scrollHeight);
953
+ const overflowingHeight = scrollHeight > (iframe?.clientHeight || 0) + 1 ? scrollHeight : 0;
954
+ return Math.ceil(Math.max(body.offsetHeight + bodyMargins + collapsedTop, bodyRect.height + bodyMargins + collapsedTop, childrenBottom, overflowingHeight));
955
+ };
956
+ const measureScrollContent = (scrollContent) => {
957
+ const contentStyle = scrollContent.ownerDocument.defaultView?.getComputedStyle(scrollContent);
958
+ const margins = contentStyle ? (Number.parseFloat(contentStyle.marginTop) || 0) + (Number.parseFloat(contentStyle.marginBottom) || 0) : 0;
959
+ let childrenBottom = 0;
960
+ const rootTop = scrollContent.getBoundingClientRect().top;
961
+ for (const child of scrollContent.children) {
962
+ const rect = child.getBoundingClientRect();
963
+ const marginBottom = Number.parseFloat(scrollContent.ownerDocument.defaultView?.getComputedStyle(child).marginBottom || "0") || 0;
964
+ childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
965
+ }
966
+ return Math.ceil(Math.max(scrollContent.scrollHeight + margins, scrollContent.offsetHeight + margins, childrenBottom + margins));
967
+ };
968
+ const isPendingPreviewApp = (iframeDocument) => {
969
+ const app = iframeDocument.getElementById("app");
970
+ return app ? app.childElementCount === 0 : !!iframe?.srcdoc;
971
+ };
972
+ const releaseBootstrapHeight = (target) => {
973
+ (0, vue.nextTick)(() => {
974
+ if (iframe === target) target.style.height = "100%";
975
+ });
976
+ };
868
977
  const measure = () => {
869
978
  frameId = 0;
870
979
  if (!iframe) return;
@@ -872,23 +981,12 @@ var useSandboxAutoHeight = (sandboxRef) => {
872
981
  const iframeWindow = iframe.contentWindow;
873
982
  const iframeDocument = iframe.contentDocument;
874
983
  if (!iframeWindow || !iframeDocument?.body) return;
875
- const { body, documentElement } = iframeDocument;
876
- const bodyStyle = iframeWindow.getComputedStyle(body);
877
- const bodyMargins = (Number.parseFloat(bodyStyle.marginTop) || 0) + (Number.parseFloat(bodyStyle.marginBottom) || 0);
878
- const rootTop = documentElement.getBoundingClientRect().top;
879
- const bodyRect = body.getBoundingClientRect();
880
- const collapsedTop = Math.max(0, bodyRect.top - rootTop);
881
- let childrenBottom = 0;
882
- for (const child of body.children) {
883
- const rect = child.getBoundingClientRect();
884
- const marginBottom = Number.parseFloat(iframeWindow.getComputedStyle(child).marginBottom) || 0;
885
- childrenBottom = Math.max(childrenBottom, rect.bottom + marginBottom - rootTop);
886
- }
887
- const scrollHeight = Math.max(body.scrollHeight, documentElement.scrollHeight);
888
- const overflowingHeight = scrollHeight > iframe.clientHeight + 1 ? scrollHeight : 0;
889
- const contentHeight = Math.ceil(Math.max(body.offsetHeight + bodyMargins + collapsedTop, bodyRect.height + bodyMargins + collapsedTop, childrenBottom, overflowingHeight));
984
+ const scrollContent = resolveScrollContent(iframeDocument);
985
+ if (!scrollContent && isPendingPreviewApp(iframeDocument)) return;
986
+ const contentHeight = scrollContent ? measureScrollContent(scrollContent) : measureDocumentContent(iframeWindow, iframeDocument);
890
987
  const nextHeight = Math.max(contentHeight, 24);
891
988
  if (height.value !== nextHeight) height.value = nextHeight;
989
+ releaseBootstrapHeight(iframe);
892
990
  } catch {
893
991
  if (height.value !== 24) height.value = 24;
894
992
  }
@@ -900,6 +998,9 @@ var useSandboxAutoHeight = (sandboxRef) => {
900
998
  const disconnectContent = () => {
901
999
  contentObserver?.disconnect();
902
1000
  contentObserver = null;
1001
+ scrollMountObserver?.disconnect();
1002
+ scrollMountObserver = null;
1003
+ observedScrollNode = null;
903
1004
  cancelMeasure();
904
1005
  };
905
1006
  const observeContent = () => {
@@ -917,7 +1018,28 @@ var useSandboxAutoHeight = (sandboxRef) => {
917
1018
  observer.observe(iframeDocument.documentElement);
918
1019
  observer.observe(iframeDocument.body);
919
1020
  contentObserver = observer;
920
- scheduleMeasure();
1021
+ const attachScrollContent = (node) => {
1022
+ if (!node || !contentObserver) return false;
1023
+ if (observedScrollNode === node) return true;
1024
+ if (observedScrollNode) contentObserver.unobserve(observedScrollNode);
1025
+ observedScrollNode = node;
1026
+ contentObserver.observe(node);
1027
+ return true;
1028
+ };
1029
+ const syncScrollContent = () => {
1030
+ attachScrollContent(resolveScrollContent(iframeDocument));
1031
+ scheduleMeasure();
1032
+ };
1033
+ scrollMountObserver = new MutationObserver(syncScrollContent);
1034
+ scrollMountObserver.observe(iframeDocument.documentElement, {
1035
+ attributes: true,
1036
+ attributeFilter: ["class"]
1037
+ });
1038
+ scrollMountObserver.observe(iframeDocument.body, {
1039
+ childList: true,
1040
+ subtree: true
1041
+ });
1042
+ syncScrollContent();
921
1043
  } catch {
922
1044
  if (height.value !== 24) height.value = 24;
923
1045
  }
@@ -933,7 +1055,7 @@ var useSandboxAutoHeight = (sandboxRef) => {
933
1055
  return;
934
1056
  }
935
1057
  iframe.addEventListener("load", handleIframeLoad);
936
- iframe.style.height = "100%";
1058
+ iframe.style.height = `${Math.max(window.innerHeight, 24)}px`;
937
1059
  observeContent();
938
1060
  };
939
1061
  const syncIframe = () => setIframe(container?.querySelector("iframe") || null);
@@ -961,6 +1083,12 @@ var useSandboxAutoHeight = (sandboxRef) => {
961
1083
  });
962
1084
  return height;
963
1085
  };
1086
+ var resolvePreviewBoxHeight = (contentHeight, verticalInset = 0, borderWidth = 1) => {
1087
+ const content = Number.isFinite(contentHeight) ? Math.max(0, contentHeight) : 0;
1088
+ const inset = Number.isFinite(verticalInset) ? Math.max(0, verticalInset) : 0;
1089
+ const border = Number.isFinite(borderWidth) ? Math.max(0, borderWidth) : 1;
1090
+ return content + inset * 2 + border * 2;
1091
+ };
964
1092
  var CLIPPING_OVERFLOW = /* @__PURE__ */ new Set([
965
1093
  "auto",
966
1094
  "scroll",
@@ -1277,6 +1405,73 @@ var useSandboxTheme = (sandboxRef) => {
1277
1405
  });
1278
1406
  };
1279
1407
  //#endregion
1408
+ //#region packages/playground/src/core/runtime/height-bridge.ts
1409
+ /**
1410
+ * 只接收本宿主当前 iframe 的高度请求,应用重评时切换会话。
1411
+ * @param sandbox 预览容器。
1412
+ * @param maxHeight 当前可用高度,普通预览默认不限制。
1413
+ * @returns 所有有效请求的最大高度。
1414
+ */
1415
+ function usePreviewHeightBridge(sandbox, maxHeight = () => Infinity) {
1416
+ const height = (0, vue.ref)(0);
1417
+ const requests = /* @__PURE__ */ new Map();
1418
+ let source = null;
1419
+ let session = "";
1420
+ let disposed = false;
1421
+ const sync = () => {
1422
+ height.value = Math.max(0, ...requests.values());
1423
+ };
1424
+ const reset = () => {
1425
+ requests.clear();
1426
+ session = "";
1427
+ source = null;
1428
+ sync();
1429
+ };
1430
+ const handleMessage = async (event) => {
1431
+ const iframe = resolveSandboxContainer(sandbox.value)?.querySelector("iframe");
1432
+ const data = event.data;
1433
+ if (!iframe?.contentWindow || event.source !== iframe.contentWindow || data?.action !== "docs:height" || typeof data.session !== "string") return;
1434
+ if (data.operation === "start") {
1435
+ reset();
1436
+ source = iframe.contentWindow;
1437
+ session = data.session;
1438
+ return;
1439
+ }
1440
+ if (source !== iframe.contentWindow || session !== data.session) return;
1441
+ if (data.operation === "stop") {
1442
+ reset();
1443
+ return;
1444
+ }
1445
+ if (!Number.isSafeInteger(data.id) || data.id <= 0) return;
1446
+ if (data.operation === "release") {
1447
+ requests.delete(data.id);
1448
+ sync();
1449
+ return;
1450
+ }
1451
+ if (data.operation !== "enable" || !Number.isFinite(data.height) || data.height <= 0) return;
1452
+ const accepted = data.height <= maxHeight();
1453
+ if (accepted) requests.set(data.id, data.height);
1454
+ sync();
1455
+ const receiver = source;
1456
+ await (0, vue.nextTick)();
1457
+ if (disposed || source !== receiver || session !== data.session) return;
1458
+ receiver?.postMessage({
1459
+ action: "docs:height:applied",
1460
+ session,
1461
+ id: data.id,
1462
+ accepted
1463
+ }, "*");
1464
+ };
1465
+ (0, vue.watch)(() => resolveSandboxContainer(sandbox.value), reset, { flush: "sync" });
1466
+ if (typeof window !== "undefined") window.addEventListener("message", handleMessage);
1467
+ (0, vue.onBeforeUnmount)(() => {
1468
+ disposed = true;
1469
+ if (typeof window !== "undefined") window.removeEventListener("message", handleMessage);
1470
+ reset();
1471
+ });
1472
+ return height;
1473
+ }
1474
+ //#endregion
1280
1475
  //#region packages/playground/src/core/runtime/toolbar.vue?vue&type=script&setup=true&lang.ts
1281
1476
  var _hoisted_1$3 = { class: "docs-playground__tools" };
1282
1477
  var _hoisted_2$3 = {
@@ -1521,7 +1716,9 @@ var BUILTIN_IMPORT_ASSETS = {
1521
1716
  "@deot/helper": "@deot/helper/dist/index.js",
1522
1717
  "normalize-wheel": "normalize-wheel-es/dist/index.mjs",
1523
1718
  "photoswipe": "photoswipe/dist/photoswipe.esm.js",
1524
- "photoswipe/lightbox": "photoswipe/dist/photoswipe-lightbox.esm.js"
1719
+ "photoswipe/lightbox": "photoswipe/dist/photoswipe-lightbox.esm.js",
1720
+ "vue": "vue/dist/vue.runtime.esm-browser.js",
1721
+ "vue/server-renderer": "vue/dist/server-renderer.esm-browser.js"
1525
1722
  };
1526
1723
  var normalizeCdnURL = (cdnURL = DEFAULT_CDN_URL) => {
1527
1724
  return cdnURL.trim().replace(/\/+$/u, "") || "https://cdn.jsdelivr.net/npm";
@@ -1532,9 +1729,7 @@ var SASS_CDN_URL = jsdelivrEsmURL("sass/+esm");
1532
1729
  var cdnAsset = (cdnURL, asset) => cdnJoin(normalizeCdnURL(cdnURL), asset);
1533
1730
  var createBuiltinImports = (cdnURL = DEFAULT_CDN_URL) => ({
1534
1731
  ...Object.fromEntries(Object.entries(BUILTIN_IMPORT_ASSETS).map(([specifier, asset]) => [specifier, cdnAsset(cdnURL, asset)])),
1535
- "lodash-es": jsdelivrEsmURL("lodash-es/+esm"),
1536
- "vue": "https://play.vuejs.org/vue.runtime.esm-browser.js",
1537
- "vue/server-renderer": "https://play.vuejs.org/server-renderer.esm-browser.js"
1732
+ "lodash-es": jsdelivrEsmURL("lodash-es/+esm")
1538
1733
  });
1539
1734
  var createBuiltinStyles = (cdnURL = DEFAULT_CDN_URL) => Object.fromEntries(PREVIEW_STYLE_ASSETS.map((asset) => [asset, cdnAsset(cdnURL, asset)]));
1540
1735
  //#endregion
@@ -1614,6 +1809,227 @@ var clearPlaygroundMaps = () => {
1614
1809
  ].forEach((store) => store.set());
1615
1810
  };
1616
1811
  //#endregion
1812
+ //#region packages/playground/src/preview-service.ts
1813
+ /** Preview iframe 内的高度服务;class 会被序列化到 iframe 执行。 */
1814
+ var PlaygroundService = class {
1815
+ target;
1816
+ vue;
1817
+ session;
1818
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1819
+ sequence = 0;
1820
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1821
+ disposed = false;
1822
+ manual;
1823
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1824
+ requests = /* @__PURE__ */ new Set();
1825
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1826
+ owners = /* @__PURE__ */ new Set();
1827
+ /**
1828
+ * @param target 当前预览窗口。
1829
+ * @param vue 与预览应用共用的 Vue 实例。
1830
+ */
1831
+ /* istanbul ignore next -- class 会被序列化到没有覆盖率运行时的 iframe。 */
1832
+ constructor(target, vue) {
1833
+ this.target = target;
1834
+ this.vue = vue;
1835
+ this.session = `${Date.now()}-${Math.random()}`;
1836
+ this.dispose = this.dispose.bind(this);
1837
+ this.post("start");
1838
+ this.target.addEventListener("pagehide", this.dispose);
1839
+ }
1840
+ /**
1841
+ * 申请手动临时高度;新请求会替换上一个手动请求。
1842
+ * @param height iframe 最小高度。
1843
+ * @returns iframe 是否在超时前应用目标高度。
1844
+ */
1845
+ enable(height) {
1846
+ this.validateHeight(height);
1847
+ this.manual?.release();
1848
+ this.manual = this.createRequest(height);
1849
+ return this.manual.ready;
1850
+ }
1851
+ /** 释放手动临时高度,不影响 `run` 创建的请求。 */
1852
+ disable() {
1853
+ this.manual?.release();
1854
+ this.manual = void 0;
1855
+ }
1856
+ /**
1857
+ * 创建等待高度后执行的事件处理函数。
1858
+ * @param height iframe 最小高度。
1859
+ * @param optionsOrHandler 可见状态配置,或最后一个 handler。
1860
+ * @param lastHandler 使用配置时的最后一个 handler。
1861
+ * @returns 保留 handler 参数和结果的异步事件处理函数。
1862
+ */
1863
+ run(height, optionsOrHandler, lastHandler) {
1864
+ this.validateHeight(height);
1865
+ if (optionsOrHandler === void 0) return (visible) => {
1866
+ if (visible) return this.enable(height);
1867
+ this.disable();
1868
+ return Promise.resolve(void 0);
1869
+ };
1870
+ const visible = (typeof optionsOrHandler === "function" ? {} : optionsOrHandler).visible;
1871
+ if (visible !== void 0 && !this.vue.isRef(visible) && typeof visible !== "function") throw new TypeError("Playground visible must be a ref or getter");
1872
+ const handler = typeof optionsOrHandler === "function" ? optionsOrHandler : lastHandler ?? (this.vue.isRef(visible) ? () => {
1873
+ visible.value = true;
1874
+ } : void 0);
1875
+ if (typeof handler !== "function") throw new TypeError("Playground run requires a handler");
1876
+ let ownerDisposed = false;
1877
+ let current;
1878
+ let pending;
1879
+ const disposeOwner = () => {
1880
+ ownerDisposed = true;
1881
+ current?.release();
1882
+ this.owners.delete(disposeOwner);
1883
+ };
1884
+ this.owners.add(disposeOwner);
1885
+ if (this.vue.getCurrentScope()) this.vue.onScopeDispose(disposeOwner);
1886
+ return (...args) => {
1887
+ if (this.disposed || ownerDisposed) return Promise.resolve(void 0);
1888
+ if (pending) return pending;
1889
+ const request = current = this.createRequest(height);
1890
+ let executionFinished = false;
1891
+ const unlock = () => {
1892
+ if (executionFinished && request.released && current === request) {
1893
+ pending = void 0;
1894
+ current = void 0;
1895
+ }
1896
+ };
1897
+ request.closed.then(unlock);
1898
+ const execution = (async () => {
1899
+ try {
1900
+ if (!await request.ready || ownerDisposed || this.disposed) return void 0;
1901
+ if (visible) request.watch(visible);
1902
+ const result = await handler(...args);
1903
+ if (!visible || !this.vue.toValue(visible)) request.release();
1904
+ return result;
1905
+ } catch (error) {
1906
+ request.release();
1907
+ throw error;
1908
+ } finally {
1909
+ executionFinished = true;
1910
+ unlock();
1911
+ }
1912
+ })();
1913
+ pending = execution;
1914
+ return execution;
1915
+ };
1916
+ }
1917
+ /** 释放当前预览应用持有的全部高度和监听。 */
1918
+ /* istanbul ignore next -- 注入测试会从序列化后的 iframe class 调用该方法。 */
1919
+ dispose() {
1920
+ if (this.disposed) return;
1921
+ this.disposed = true;
1922
+ for (const disposeOwner of this.owners) disposeOwner();
1923
+ for (const request of this.requests) request.release();
1924
+ this.post("stop");
1925
+ this.target.removeEventListener("pagehide", this.dispose);
1926
+ if (this.target.$docsPlayground === this) delete this.target.$docsPlayground;
1927
+ }
1928
+ /** @param height iframe 最小高度。 */
1929
+ validateHeight(height) {
1930
+ if (!Number.isFinite(height) || height <= 0) throw new TypeError("Playground height must be a positive finite number");
1931
+ }
1932
+ /**
1933
+ * @param operation 请求操作。
1934
+ * @param id 请求标识。
1935
+ * @param height iframe 最小高度。
1936
+ */
1937
+ /* istanbul ignore next -- 构造和销毁时会从序列化后的 iframe class 调用该方法。 */
1938
+ post(operation, id = 0, height = 0) {
1939
+ this.target.parent.postMessage({
1940
+ action: "docs:height",
1941
+ session: this.session,
1942
+ operation,
1943
+ id,
1944
+ height
1945
+ }, "*");
1946
+ }
1947
+ /**
1948
+ * @param height iframe 最小高度。
1949
+ * @returns 内部等待与释放句柄。
1950
+ */
1951
+ createRequest(height) {
1952
+ this.validateHeight(height);
1953
+ const id = ++this.sequence;
1954
+ let released = false;
1955
+ let settled = false;
1956
+ let frame = 0;
1957
+ let timer = 0;
1958
+ let stopWatch;
1959
+ let resolveReady;
1960
+ let resolveClosed;
1961
+ const ready = new Promise((resolve) => {
1962
+ resolveReady = resolve;
1963
+ });
1964
+ const closed = new Promise((resolve) => {
1965
+ resolveClosed = resolve;
1966
+ });
1967
+ const stopWaiting = () => {
1968
+ this.target.cancelAnimationFrame(frame);
1969
+ if (timer) {
1970
+ this.target.clearTimeout(timer);
1971
+ timer = 0;
1972
+ }
1973
+ this.target.removeEventListener("message", onMessage);
1974
+ };
1975
+ const release = () => {
1976
+ if (released) return;
1977
+ released = true;
1978
+ stopWaiting();
1979
+ stopWatch?.();
1980
+ resolveReady(false);
1981
+ resolveClosed();
1982
+ this.requests.delete(request);
1983
+ this.post("release", id);
1984
+ };
1985
+ const check = () => {
1986
+ if (released) return;
1987
+ if (this.target.innerHeight >= height) {
1988
+ settled = true;
1989
+ stopWaiting();
1990
+ resolveReady(true);
1991
+ } else frame = this.target.requestAnimationFrame(check);
1992
+ };
1993
+ const onMessage = (event) => {
1994
+ const data = event.data;
1995
+ if (event.source !== this.target.parent || data?.action !== "docs:height:applied" || data.session !== this.session || data.id !== id || settled) return;
1996
+ if (!data.accepted) release();
1997
+ else check();
1998
+ };
1999
+ const request = {
2000
+ ready,
2001
+ closed,
2002
+ get released() {
2003
+ return released;
2004
+ },
2005
+ release,
2006
+ watch: (visible) => {
2007
+ stopWatch = this.vue.watch(() => this.vue.toValue(visible), (value) => {
2008
+ if (!value) release();
2009
+ }, { flush: "sync" });
2010
+ }
2011
+ };
2012
+ this.requests.add(request);
2013
+ if (this.disposed) release();
2014
+ else {
2015
+ this.target.addEventListener("message", onMessage);
2016
+ timer = this.target.setTimeout(() => {
2017
+ timer = 0;
2018
+ release();
2019
+ }, 2e3);
2020
+ this.post("enable", id, height);
2021
+ }
2022
+ return request;
2023
+ }
2024
+ };
2025
+ var PREVIEW_SERVICE_IMPORT_CODE = "import * as __docsPreviewVue from \"vue\"";
2026
+ var PREVIEW_SERVICE_USE_CODE = [
2027
+ `;const __docsPlayground=new (${PlaygroundService.toString()})(window,__docsPreviewVue);`,
2028
+ "app.provide(\"docs:playground\",__docsPlayground);",
2029
+ "window.$docsPlayground=__docsPlayground;",
2030
+ "app.mixin({beforeUnmount(){if(this===this.$root)__docsPlayground.dispose()}});"
2031
+ ].join("");
2032
+ //#endregion
1617
2033
  //#region packages/playground/src/core/scss.ts
1618
2034
  var SCHEME = "docs-playground";
1619
2035
  var STYLE_EXTS = [".scss", ".sass"];
@@ -1830,6 +2246,7 @@ var bindPlaygroundScss = (store) => {
1830
2246
  //#region packages/playground/src/core/store.ts
1831
2247
  var DOCS_LINK_IMPORT_CODE = "import { h as __docsH } from \"vue\"";
1832
2248
  var DOCS_LINK_USE_CODE = [
2249
+ PREVIEW_SCROLL_RESET_CODE,
1833
2250
  "app.component(\"DocsLink\",{",
1834
2251
  "props:{to:{type:String,default:\"\"}},",
1835
2252
  "setup(props,{slots}){return()=>__docsH(\"a\",{",
@@ -1846,26 +2263,32 @@ var escapeHtmlAttr = (value) => value.replace(/&/gu, "&amp;").replace(/"/gu, "&q
1846
2263
  * `--vc-background-color-light` 不同,否则会出现色带割裂。
1847
2264
  */
1848
2265
  var PLAYGROUND_RUNTIME_CANVAS_BACKGROUND = "var(--vc-background-color-light, var(--docs-background-color, #fff))";
1849
- var createRuntimePreviewOptions = (cdnURL = DEFAULT_CDN_URL) => {
2266
+ var createRuntimePreviewOptions = (cdnURL = DEFAULT_CDN_URL, options = {}) => {
2267
+ const previewScroller = options.previewScroller === true;
2268
+ const hrefs = Object.values({
2269
+ ...createBuiltinStyles(cdnURL),
2270
+ ...getPlaygroundSiteStyles(),
2271
+ ...getPlaygroundStyleOverrides()
2272
+ });
2273
+ const styleBlocks = [
2274
+ `html,body{height:auto;min-height:0;background:${PLAYGROUND_RUNTIME_CANVAS_BACKGROUND}}`,
2275
+ "body{color:var(--vc-foreground-color,#080f20)}",
2276
+ ...previewScroller ? [PREVIEW_SCROLLER_STYLE] : []
2277
+ ];
1850
2278
  return {
1851
2279
  showRuntimeError: false,
1852
2280
  showRuntimeWarning: false,
1853
2281
  headHTML: [
1854
2282
  SANDBOX_RUNTIME_ERROR_CAPTURE_HTML,
1855
2283
  "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
1856
- ...Object.values({
1857
- ...createBuiltinStyles(cdnURL),
1858
- ...getPlaygroundSiteStyles(),
1859
- ...getPlaygroundStyleOverrides()
1860
- }).map((href) => `<link rel="stylesheet" href="${escapeHtmlAttr(href)}">`),
2284
+ ...hrefs.map((href) => `<link rel="stylesheet" href="${escapeHtmlAttr(href)}">`),
1861
2285
  "<style>",
1862
- `html,body{height:auto;min-height:0;background:${PLAYGROUND_RUNTIME_CANVAS_BACKGROUND}}`,
1863
- "body{color:var(--vc-foreground-color,#080f20)}",
2286
+ ...styleBlocks,
1864
2287
  "</style>"
1865
2288
  ].join("\n"),
1866
2289
  customCode: {
1867
- importCode: DOCS_LINK_IMPORT_CODE,
1868
- useCode: DOCS_LINK_USE_CODE
2290
+ importCode: `${DOCS_LINK_IMPORT_CODE}\n${PREVIEW_SERVICE_IMPORT_CODE}`,
2291
+ useCode: `${DOCS_LINK_USE_CODE}\n${PREVIEW_SERVICE_USE_CODE}`
1869
2292
  }
1870
2293
  };
1871
2294
  };
@@ -1946,6 +2369,8 @@ var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
1946
2369
  const { t } = (0, _deot_docs_locale.useLocale)();
1947
2370
  const currentViewport = (0, vue.ref)(props.viewport);
1948
2371
  const sandboxRef = (0, vue.ref)(null);
2372
+ const availableHeight = () => Math.max(0, getWindowInnerSize().height - 40 - 44);
2373
+ const temporaryHeight = usePreviewHeightBridge(sandboxRef, availableHeight);
1949
2374
  const sandboxKey = (0, vue.ref)(0);
1950
2375
  const layoutTick = (0, vue.ref)(0);
1951
2376
  const runtimeError = useSandboxRuntimeErrorGuard(sandboxRef);
@@ -1957,7 +2382,16 @@ var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
1957
2382
  });
1958
2383
  const layout = (0, vue.computed)(() => {
1959
2384
  layoutTick.value;
1960
- return resolvePopupLayout(currentViewport.value);
2385
+ const base = resolvePopupLayout(currentViewport.value);
2386
+ if (!temporaryHeight.value) return base;
2387
+ const canvasHeight = Math.min(availableHeight(), Math.max(base.canvasHeight, temporaryHeight.value));
2388
+ const shellHeight = Math.max(base.shellHeight, canvasHeight + 44);
2389
+ return {
2390
+ ...base,
2391
+ canvasHeight,
2392
+ shellHeight,
2393
+ bodyHeight: shellHeight - 44
2394
+ };
1961
2395
  });
1962
2396
  const shellStyle = (0, vue.computed)(() => ({
1963
2397
  width: `${layout.value.shellWidth}px`,
@@ -2121,6 +2555,10 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
2121
2555
  options: {},
2122
2556
  previewInset: { default: 10 },
2123
2557
  previewOptions: {},
2558
+ previewScroller: {
2559
+ type: Boolean,
2560
+ default: false
2561
+ },
2124
2562
  styleless: {
2125
2563
  type: Boolean,
2126
2564
  default: false
@@ -2154,7 +2592,7 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
2154
2592
  const emit = __emit;
2155
2593
  const joinCode = (...values) => values.filter(Boolean).join("\n");
2156
2594
  const mergePreviewOptions = (extraHeadHTML) => {
2157
- const runtimePreviewOptions = createRuntimePreviewOptions(props.options.cdnURL);
2595
+ const runtimePreviewOptions = createRuntimePreviewOptions(props.options.cdnURL, { previewScroller: props.previewScroller });
2158
2596
  return {
2159
2597
  ...runtimePreviewOptions,
2160
2598
  ...props.previewOptions,
@@ -2164,8 +2602,8 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
2164
2602
  extraHeadHTML
2165
2603
  ].filter(Boolean).join("\n"),
2166
2604
  customCode: {
2167
- importCode: joinCode(runtimePreviewOptions.customCode?.importCode, props.previewOptions?.customCode?.importCode),
2168
- useCode: joinCode(runtimePreviewOptions.customCode?.useCode, props.previewOptions?.customCode?.useCode)
2605
+ importCode: joinCode(runtimePreviewOptions.customCode?.importCode, props.previewOptions?.customCode?.importCode, props.previewScroller ? PREVIEW_SCROLL_RESET_CODE : void 0, props.previewScroller ? PREVIEW_SCROLLER_IMPORT_CODE : void 0),
2606
+ useCode: joinCode(runtimePreviewOptions.customCode?.useCode, props.previewOptions?.customCode?.useCode, props.previewScroller ? PREVIEW_SCROLLER_USE_CODE : void 0)
2169
2607
  }
2170
2608
  };
2171
2609
  };
@@ -2181,6 +2619,7 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
2181
2619
  const copyValue = (0, vue.computed)(() => props.files[props.entry] || "");
2182
2620
  const store = createRuntimeStore(props.files, props.entry, props.options);
2183
2621
  const sandboxRef = (0, vue.ref)(null);
2622
+ const temporaryHeight = usePreviewHeightBridge(sandboxRef);
2184
2623
  const sandboxKey = (0, vue.ref)(0);
2185
2624
  const runtimeHeight = useSandboxAutoHeight(sandboxRef);
2186
2625
  const runtimeError = useSandboxRuntimeErrorGuard(sandboxRef);
@@ -2208,13 +2647,21 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
2208
2647
  const error = root.querySelector(".docs-playground__runtime-error");
2209
2648
  return (header instanceof HTMLElement ? header.offsetHeight : 0) + (error instanceof HTMLElement ? error.offsetHeight : 0);
2210
2649
  };
2650
+ const normalizedPreviewInset = (0, vue.computed)(() => {
2651
+ const value = props.previewInset;
2652
+ if (typeof value === "number") return Number.isFinite(value) && value >= 0 ? [value, value] : [10, 10];
2653
+ if (Array.isArray(value) && value.length === 2 && value.every((item) => typeof item === "number" && Number.isFinite(item) && item >= 0)) return [value[0], value[1]];
2654
+ return [10, 10];
2655
+ });
2211
2656
  const measureExpandedPreviewHeight = () => {
2212
2657
  if (typeof window === "undefined") return runtimeHeight.value;
2213
2658
  const chromeHeight = measurePreviewChromeHeight();
2659
+ const viewport = getVisibleViewportRect(runtimeRoot.value);
2660
+ const [vertical] = normalizedPreviewInset.value;
2214
2661
  return resolveExpandedPreviewHeight(true, resolveRemainingPreviewHeight({
2215
- viewportHeight: getVisibleViewportRect(runtimeRoot.value).height,
2662
+ viewportHeight: viewport.height,
2216
2663
  chromeHeight
2217
- }));
2664
+ }) - vertical * 2 - 2);
2218
2665
  };
2219
2666
  const syncFrozenExpandedHeight = () => {
2220
2667
  if (!previewExpanded.value || props.expandable !== true) return;
@@ -2226,13 +2673,14 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
2226
2673
  }
2227
2674
  const canExpandPreview = (0, vue.computed)(() => !props.styleless && isPlaygroundExpandable(props.expandable));
2228
2675
  const expandLabel = (0, vue.computed)(() => t(previewExpanded.value ? "playground.runtime.collapsePreview" : "playground.runtime.expandPreview"));
2229
- const desiredViewportHeight = (0, vue.computed)(() => {
2676
+ const baseViewportHeight = (0, vue.computed)(() => {
2230
2677
  if (previewExpanded.value && props.expandable === true) return frozenExpandedHeight.value;
2231
2678
  if (previewExpanded.value && isPlaygroundExpandable(props.expandable)) return resolveExpandedPreviewHeight(props.expandable, 0);
2232
2679
  const fixedHeight = getViewportHeight(props.viewport);
2233
2680
  if (fixedHeight) return fixedHeight;
2234
2681
  return runtimeHeight.value;
2235
2682
  });
2683
+ const desiredViewportHeight = (0, vue.computed)(() => Math.max(baseViewportHeight.value, temporaryHeight.value));
2236
2684
  const scrollExpandedPreviewIntoView = () => {
2237
2685
  scrollPlaygroundToViewportStart(runtimeRoot.value);
2238
2686
  };
@@ -2254,16 +2702,10 @@ var runtime_default = /* @__PURE__ */ (0, vue.defineComponent)({
2254
2702
  frozenExpandedHeight.value = 0;
2255
2703
  }
2256
2704
  });
2257
- const normalizedPreviewInset = (0, vue.computed)(() => {
2258
- const value = props.previewInset;
2259
- if (typeof value === "number") return Number.isFinite(value) && value >= 0 ? [value, value] : [10, 10];
2260
- if (Array.isArray(value) && value.length === 2 && value.every((item) => typeof item === "number" && Number.isFinite(item) && item >= 0)) return [value[0], value[1]];
2261
- return [10, 10];
2262
- });
2263
2705
  const previewStyle = (0, vue.computed)(() => {
2264
2706
  const [vertical, horizontal] = normalizedPreviewInset.value;
2265
2707
  return {
2266
- height: `${desiredViewportHeight.value + vertical * 2}px`,
2708
+ height: `${resolvePreviewBoxHeight(desiredViewportHeight.value, vertical)}px`,
2267
2709
  padding: vertical === horizontal ? `${vertical}px` : `${vertical}px ${horizontal}px`,
2268
2710
  background: PLAYGROUND_RUNTIME_CANVAS_BACKGROUND
2269
2711
  };
@@ -2476,6 +2918,10 @@ var playground_default = /* @__PURE__ */ (0, vue.defineComponent)({
2476
2918
  expandable: { type: [Boolean, Number] },
2477
2919
  title: { default: "" },
2478
2920
  id: { default: "" },
2921
+ previewScroller: {
2922
+ type: Boolean,
2923
+ default: false
2924
+ },
2479
2925
  previewOptions: {},
2480
2926
  locale: {}
2481
2927
  },
@@ -2633,6 +3079,7 @@ var playground_default = /* @__PURE__ */ (0, vue.defineComponent)({
2633
3079
  options: __props.options,
2634
3080
  "preview-options": __props.previewOptions,
2635
3081
  "preview-inset": __props.previewInset,
3082
+ "preview-scroller": __props.previewScroller,
2636
3083
  expandable: __props.expandable,
2637
3084
  styleless: true,
2638
3085
  title: __props.title,
@@ -2648,6 +3095,7 @@ var playground_default = /* @__PURE__ */ (0, vue.defineComponent)({
2648
3095
  "options",
2649
3096
  "preview-options",
2650
3097
  "preview-inset",
3098
+ "preview-scroller",
2651
3099
  "expandable",
2652
3100
  "title",
2653
3101
  "id",
@@ -2700,6 +3148,7 @@ var playground_default = /* @__PURE__ */ (0, vue.defineComponent)({
2700
3148
  options: __props.options,
2701
3149
  "preview-options": __props.previewOptions,
2702
3150
  "preview-inset": __props.previewInset,
3151
+ "preview-scroller": __props.previewScroller,
2703
3152
  expandable: __props.expandable,
2704
3153
  "hide-chrome": true,
2705
3154
  title: __props.title,
@@ -2717,6 +3166,7 @@ var playground_default = /* @__PURE__ */ (0, vue.defineComponent)({
2717
3166
  "options",
2718
3167
  "preview-options",
2719
3168
  "preview-inset",
3169
+ "preview-scroller",
2720
3170
  "expandable",
2721
3171
  "title",
2722
3172
  "id",
@@ -2748,7 +3198,11 @@ var playground_default = /* @__PURE__ */ (0, vue.defineComponent)({
2748
3198
  exports.CodePreview = code_preview_default;
2749
3199
  exports.DEFAULT_CDN_URL = DEFAULT_CDN_URL;
2750
3200
  exports.Editor = Editor;
3201
+ exports.PREVIEW_SCROLLER_USE_CODE = PREVIEW_SCROLLER_USE_CODE;
3202
+ exports.PREVIEW_SCROLL_CONTENT_CLASS = PREVIEW_SCROLL_CONTENT_CLASS;
3203
+ exports.PREVIEW_SCROLL_HTML_CLASS = PREVIEW_SCROLL_HTML_CLASS;
2751
3204
  exports.Playground = playground_default;
3205
+ exports.PlaygroundService = PlaygroundService;
2752
3206
  exports.applyPlaygroundImportMapOverride = applyPlaygroundImportMapOverride;
2753
3207
  exports.applyPlaygroundStyleOverride = applyPlaygroundStyleOverride;
2754
3208
  exports.clearPlaygroundMaps = clearPlaygroundMaps;