@takazudo/zudo-doc 5.14.0 → 5.16.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/CHANGELOG.md +40 -0
- package/dist/asset-components/asset-card.js +3 -1
- package/dist/asset-components/asset-code.js +2 -0
- package/dist/asset-components/shared.d.ts +5 -0
- package/dist/asset-components/shared.js +7 -0
- package/dist/asset-index-page/index.d.ts +1 -0
- package/dist/asset-index-page/index.js +10 -9
- package/dist/asset-page/index.d.ts +20 -2
- package/dist/asset-page/index.js +43 -24
- package/dist/asset-path/index.d.ts +2 -0
- package/dist/asset-path/index.js +11 -1
- package/dist/chrome/derive.js +31 -6
- package/dist/compiled.css +12 -0
- package/dist/config.d.ts +15 -6
- package/dist/config.js +2 -0
- package/dist/content/content-link.d.ts +5 -1
- package/dist/content/content-link.js +4 -1
- package/dist/doc-content-header/index.js +1 -1
- package/dist/doc-page-shell/index.js +1 -1
- package/dist/factory-context/index.d.ts +7 -3
- package/dist/header-with-defaults/index.js +2 -0
- package/dist/home-page/index.js +1 -1
- package/dist/html-preview-wrapper/html-preview-wrapper.d.ts +41 -7
- package/dist/html-preview-wrapper/html-preview-wrapper.js +86 -2
- package/dist/html-preview-wrapper/html-preview.d.ts +29 -4
- package/dist/html-preview-wrapper/html-preview.js +64 -6
- package/dist/html-preview-wrapper/index.d.ts +1 -1
- package/dist/html-preview-wrapper/preview-auto-height.d.ts +24 -0
- package/dist/html-preview-wrapper/preview-auto-height.js +120 -0
- package/dist/html-preview-wrapper/preview-base.d.ts +21 -1
- package/dist/html-preview-wrapper/preview-base.js +99 -50
- package/dist/i18n-defaults/index.d.ts +5 -3
- package/dist/i18n-defaults/index.js +89 -0
- package/dist/i18n-version/language-switcher.d.ts +10 -5
- package/dist/i18n-version/language-switcher.js +115 -22
- package/dist/i18n-version/types.d.ts +1 -1
- package/dist/mdx-components/index.d.ts +4 -0
- package/dist/mdx-components/index.js +11 -2
- package/dist/nav-indexing/versions-page-content.js +1 -1
- package/dist/plugins/claude-resources.js +15 -1
- package/dist/plugins/codex-resources.js +15 -1
- package/dist/plugins/internal/asset-viewer/asset-pages.d.ts +53 -0
- package/dist/plugins/internal/asset-viewer/asset-pages.js +98 -0
- package/dist/plugins/internal/asset-viewer/scan.d.ts +18 -1
- package/dist/plugins/internal/asset-viewer/scan.js +34 -27
- package/dist/plugins/internal/claude-resources/generate.d.ts +9 -0
- package/dist/plugins/internal/claude-resources/generate.js +188 -18
- package/dist/plugins/internal/claude-resources/index.d.ts +9 -0
- package/dist/plugins/internal/claude-resources/index.js +13 -1
- package/dist/plugins/internal/codex-resources/agents-md.js +15 -2
- package/dist/plugins/internal/codex-resources/agents.js +15 -2
- package/dist/plugins/internal/codex-resources/config.js +15 -2
- package/dist/plugins/internal/codex-resources/generate.d.ts +9 -0
- package/dist/plugins/internal/codex-resources/generate.js +19 -7
- package/dist/plugins/internal/codex-resources/hooks.js +15 -2
- package/dist/plugins/internal/codex-resources/index.d.ts +9 -0
- package/dist/plugins/internal/codex-resources/index.js +13 -1
- package/dist/plugins/internal/codex-resources/overview.js +136 -7
- package/dist/plugins/internal/codex-resources/rules.js +15 -2
- package/dist/plugins/internal/codex-resources/skills.js +16 -3
- package/dist/plugins/internal/llms-txt/assets.d.ts +24 -0
- package/dist/plugins/internal/llms-txt/assets.js +65 -0
- package/dist/plugins/internal/llms-txt/dev-middleware.d.ts +5 -0
- package/dist/plugins/internal/llms-txt/dev-middleware.js +9 -1
- package/dist/plugins/internal/llms-txt/emit.js +21 -5
- package/dist/plugins/internal/llms-txt/generate.d.ts +3 -3
- package/dist/plugins/internal/llms-txt/generate.js +55 -2
- package/dist/plugins/internal/llms-txt/index.d.ts +4 -1
- package/dist/plugins/internal/llms-txt/index.js +4 -0
- package/dist/plugins/internal/llms-txt/types.d.ts +24 -0
- package/dist/plugins/internal/llms-txt/types.js +4 -0
- package/dist/plugins/internal/resource-docs-shared/fs.d.ts +33 -0
- package/dist/plugins/internal/resource-docs-shared/fs.js +82 -1
- package/dist/plugins/internal/resource-docs-shared/index.d.ts +4 -2
- package/dist/plugins/internal/resource-docs-shared/index.js +21 -1
- package/dist/plugins/internal/resource-docs-shared/labels.d.ts +24 -0
- package/dist/plugins/internal/resource-docs-shared/labels.js +14 -0
- package/dist/plugins/internal/resource-docs-shared/locale-routes.d.ts +16 -0
- package/dist/plugins/internal/resource-docs-shared/locale-routes.js +14 -0
- package/dist/plugins/internal/resource-docs-shared/mdx.d.ts +7 -1
- package/dist/plugins/internal/resource-docs-shared/mdx.js +2 -2
- package/dist/plugins/internal/search-index/collect.js +46 -1
- package/dist/plugins/internal/search-index/types.d.ts +6 -1
- package/dist/plugins/llms-txt.js +9 -1
- package/dist/plugins/routes.js +14 -0
- package/dist/plugins/search-index.js +10 -1
- package/dist/preset.d.ts +9 -1
- package/dist/preset.js +25 -4
- package/dist/route-context/index.js +5 -17
- package/dist/route-enumerators/index.d.ts +14 -1
- package/dist/route-enumerators/index.js +38 -0
- package/dist/routes/locale-files-path.d.ts +29 -0
- package/dist/routes/locale-files-path.js +52 -0
- package/dist/safelist.css +1 -1
- package/dist/settings.d.ts +15 -0
- package/dist/tag-pages/index.js +2 -2
- package/eject/asset-components/asset-card.tsx +2 -0
- package/eject/asset-components/asset-code.tsx +2 -0
- package/eject/asset-components/shared.tsx +16 -0
- package/package.json +13 -9
- package/routes-src/locale-files-path.tsx +73 -0
|
@@ -24,7 +24,9 @@ function createContentLink({
|
|
|
24
24
|
base,
|
|
25
25
|
assetManifest,
|
|
26
26
|
routePrefix,
|
|
27
|
-
dir
|
|
27
|
+
dir,
|
|
28
|
+
locale,
|
|
29
|
+
isDefaultLocaleOnlyPath
|
|
28
30
|
}) {
|
|
29
31
|
return function ManifestAwareContentLink(props) {
|
|
30
32
|
const { href, className, children } = props;
|
|
@@ -43,6 +45,7 @@ function createContentLink({
|
|
|
43
45
|
base,
|
|
44
46
|
routePrefix,
|
|
45
47
|
path: entry.path,
|
|
48
|
+
locale: isDefaultLocaleOnlyPath?.(`/${routePrefix}/${entry.path}`) ? void 0 : locale,
|
|
46
49
|
fragment: decoded.fragment
|
|
47
50
|
}),
|
|
48
51
|
children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-baseline gap-x-hsp-xs font-mono", children: [
|
|
@@ -20,7 +20,7 @@ function createDocContentHeader(ctx) {
|
|
|
20
20
|
version
|
|
21
21
|
}) {
|
|
22
22
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23
|
-
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold border-b border-fg pb-vsp-xs mb-vsp-xs", children: entry.data.title }),
|
|
23
|
+
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold border-b border-fg pb-vsp-xs mb-vsp-xs break-words", children: entry.data.title }),
|
|
24
24
|
docContentHeaderExtras?.({
|
|
25
25
|
entry,
|
|
26
26
|
slug,
|
|
@@ -176,7 +176,7 @@ function createDocPageShell(ctx) {
|
|
|
176
176
|
Fragment (not <div>) so children become direct children of
|
|
177
177
|
<article class="zd-content">, picking up the flow-space rule. */
|
|
178
178
|
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
179
|
-
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold border-b border-fg pb-vsp-xs mb-vsp-xs", children: autoIndexLabel }),
|
|
179
|
+
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold border-b border-fg pb-vsp-xs mb-vsp-xs break-words", children: autoIndexLabel }),
|
|
180
180
|
metainfoSlot,
|
|
181
181
|
description && /* @__PURE__ */ jsx("p", { class: "mb-vsp-lg text-title text-muted", "data-doc-description": true, children: description }),
|
|
182
182
|
/* @__PURE__ */ jsx(NavCardGrid, { children: autoIndexChildren ?? [] })
|
|
@@ -27,8 +27,9 @@ export interface FactoryI18n {
|
|
|
27
27
|
/** Display label for a locale (e.g. `"EN"`, `"日本語"`). */
|
|
28
28
|
getLocaleLabel: (locale: string) => string;
|
|
29
29
|
/**
|
|
30
|
-
* Translate a UI string key for a locale, falling back
|
|
31
|
-
* then the key
|
|
30
|
+
* Translate a UI string key for a locale, falling back through the configured
|
|
31
|
+
* default locale, package English, then the raw key. Optional: factories
|
|
32
|
+
* that emit no UI strings omit it.
|
|
32
33
|
*/
|
|
33
34
|
t?: (key: string, locale?: string) => string;
|
|
34
35
|
}
|
|
@@ -136,7 +137,10 @@ export interface RouteContext<S = Settings> extends UrlHelpers, NavSourceDocsAPI
|
|
|
136
137
|
} | undefined;
|
|
137
138
|
/** Display label for a locale. */
|
|
138
139
|
getLocaleLabel(locale: string): string;
|
|
139
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Translate a UI string key for a locale (falls back through the configured
|
|
142
|
+
* default locale, package English, then the raw key).
|
|
143
|
+
*/
|
|
140
144
|
t(key: string, locale?: string): string;
|
|
141
145
|
/** The full URL-helper bag (also spread as own members via `extends`). */
|
|
142
146
|
urlHelpers: UrlHelpers;
|
package/dist/home-page/index.js
CHANGED
|
@@ -117,7 +117,7 @@ function createHomePageView(ctx) {
|
|
|
117
117
|
}
|
|
118
118
|
) : null,
|
|
119
119
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
120
|
-
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-2xs", children: settings.siteName }),
|
|
120
|
+
/* @__PURE__ */ jsx("h1", { class: "text-heading font-bold mb-vsp-2xs break-words", children: settings.siteName }),
|
|
121
121
|
/* @__PURE__ */ jsx("p", { class: "text-muted text-small mb-vsp-sm", children: settings.siteDescription }),
|
|
122
122
|
/* @__PURE__ */ jsx("div", { class: "flex items-center justify-center lg:justify-start gap-hsp-md text-small", children: rowItems.map((item, index) => /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
123
123
|
index > 0 && /* @__PURE__ */ jsx("span", { class: "text-muted", children: "/" }),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @jsxRuntime automatic */
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
3
|
import type { VNode } from "preact";
|
|
4
|
+
import { type HtmlPreviewLabels } from "./html-preview.js";
|
|
4
5
|
/**
|
|
5
6
|
* Global HTML preview configuration. Mirrors the `settings.htmlPreview`
|
|
6
7
|
* shape from the host project so callers can pass the resolved config
|
|
@@ -15,6 +16,20 @@ export interface HtmlPreviewGlobalConfig {
|
|
|
15
16
|
js?: string;
|
|
16
17
|
}
|
|
17
18
|
export interface HtmlPreviewWrapperProps {
|
|
19
|
+
/**
|
|
20
|
+
* Controls when the preview subtree is rendered.
|
|
21
|
+
*
|
|
22
|
+
* `"eager"` (and omission) preserves the server-rendered iframe and
|
|
23
|
+
* hydrates it when visible. `"visible"` emits only an inert height
|
|
24
|
+
* reservation during SSR and renders the preview on the client through
|
|
25
|
+
* zfb's skip-SSR island path.
|
|
26
|
+
*
|
|
27
|
+
* This is a component lifecycle policy and is not forwarded as the native
|
|
28
|
+
* iframe `loading` attribute.
|
|
29
|
+
*
|
|
30
|
+
* @default "eager"
|
|
31
|
+
*/
|
|
32
|
+
loading?: "eager" | "visible";
|
|
18
33
|
/**
|
|
19
34
|
* Site-wide HTML preview configuration (resolved from
|
|
20
35
|
* `settings.htmlPreview` by the caller). When provided, its
|
|
@@ -36,10 +51,24 @@ export interface HtmlPreviewWrapperProps {
|
|
|
36
51
|
js?: string;
|
|
37
52
|
/** Optional title displayed in the preview title bar. */
|
|
38
53
|
title?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Language tag for the generated preview document. Any nonblank tag is
|
|
56
|
+
* forwarded as-is; omission or a blank value falls back to `"en"` in
|
|
57
|
+
* `<HtmlPreview>`. Bound consumers should pass their active route locale.
|
|
58
|
+
*
|
|
59
|
+
* @default "en"
|
|
60
|
+
*/
|
|
61
|
+
lang?: string;
|
|
39
62
|
/** Fixed iframe height in pixels. Auto-sizes when omitted. */
|
|
40
63
|
height?: number;
|
|
41
64
|
/** When true, the code section is expanded by default. */
|
|
42
65
|
defaultOpen?: boolean;
|
|
66
|
+
/** Localized labels for viewport, source, and iframe controls. */
|
|
67
|
+
labels?: Partial<HtmlPreviewLabels>;
|
|
68
|
+
/** Whether the source toggle and code panel are rendered. @default true */
|
|
69
|
+
showSource?: boolean;
|
|
70
|
+
/** Whether the viewport preset controls are rendered. @default true */
|
|
71
|
+
showViewportControls?: boolean;
|
|
43
72
|
/**
|
|
44
73
|
* Forwarded to `<HtmlPreview>`. When true, makes the preview document's
|
|
45
74
|
* `html`/`body` stretch to 100% height. Interacts with auto-height — pair
|
|
@@ -94,6 +123,7 @@ export interface HtmlPreviewWrapperProps {
|
|
|
94
123
|
*/
|
|
95
124
|
showResources?: boolean;
|
|
96
125
|
}
|
|
126
|
+
type HtmlPreviewWrapperInnerProps = Omit<HtmlPreviewWrapperProps, "loading">;
|
|
97
127
|
/**
|
|
98
128
|
* Bare HTML preview body — the actual island **hydration target**.
|
|
99
129
|
*
|
|
@@ -121,7 +151,7 @@ export interface HtmlPreviewWrapperProps {
|
|
|
121
151
|
* (`HtmlPreviewWrapperInner`) and is exported, so the marker resolves to
|
|
122
152
|
* THIS bare component and the bundle hydrates it in-place.
|
|
123
153
|
*/
|
|
124
|
-
export declare function HtmlPreviewWrapperInner(props:
|
|
154
|
+
export declare function HtmlPreviewWrapperInner(props: HtmlPreviewWrapperInnerProps): VNode;
|
|
125
155
|
export declare namespace HtmlPreviewWrapperInner {
|
|
126
156
|
var displayName: string;
|
|
127
157
|
}
|
|
@@ -129,12 +159,15 @@ export declare namespace HtmlPreviewWrapperInner {
|
|
|
129
159
|
* HTML preview wrapper component — the public MDX-registered binding
|
|
130
160
|
* (`HtmlPreview: HtmlPreviewWrapper`).
|
|
131
161
|
*
|
|
132
|
-
*
|
|
133
|
-
* mirroring the legacy `client:visible` hydration
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* client
|
|
162
|
+
* Eager mode wraps the bare `HtmlPreviewWrapperInner` in
|
|
163
|
+
* `<Island when="visible">`, mirroring the legacy `client:visible` hydration
|
|
164
|
+
* timing while preserving the complete server-rendered preview. Visible mode
|
|
165
|
+
* uses zfb's skip-SSR fallback path: static output contains only an inert
|
|
166
|
+
* nonzero reservation, while the real serializable inner props remain on the
|
|
167
|
+
* island marker for the client mount. zfb intentionally mounts skip-SSR
|
|
168
|
+
* islands immediately, so the bare inner target keeps that reservation in
|
|
169
|
+
* place and applies its own one-shot IntersectionObserver gate before it
|
|
170
|
+
* instantiates the preview subtree; missing observer support fails open.
|
|
138
171
|
*
|
|
139
172
|
* The public export name and signature are unchanged from before the
|
|
140
173
|
* zudolab/zudo-doc#1925 fix, so existing consumers that register
|
|
@@ -142,3 +175,4 @@ export declare namespace HtmlPreviewWrapperInner {
|
|
|
142
175
|
* with no call-site change.
|
|
143
176
|
*/
|
|
144
177
|
export declare function HtmlPreviewWrapper(props: HtmlPreviewWrapperProps): VNode;
|
|
178
|
+
export {};
|
|
@@ -1,24 +1,91 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "preact/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
3
4
|
import { Island } from "@takazudo/zfb";
|
|
4
5
|
import { HtmlPreview } from "./html-preview.js";
|
|
6
|
+
const VISIBLE_MOUNT_PROP = "__zudoDocVisibleMount";
|
|
7
|
+
function reservationHeight(height) {
|
|
8
|
+
return height != null && height > 0 ? height : 200;
|
|
9
|
+
}
|
|
10
|
+
function HtmlPreviewReservation({
|
|
11
|
+
height,
|
|
12
|
+
reservationRef
|
|
13
|
+
}) {
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
"div",
|
|
16
|
+
{
|
|
17
|
+
ref: reservationRef,
|
|
18
|
+
"aria-hidden": "true",
|
|
19
|
+
"data-zd-html-preview-reservation": true,
|
|
20
|
+
style: { height: reservationHeight(height) }
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
5
24
|
function HtmlPreviewWrapperInner(props) {
|
|
25
|
+
const runtimeProps = props;
|
|
26
|
+
const deferUntilVisible = runtimeProps[VISIBLE_MOUNT_PROP] === true;
|
|
27
|
+
const reservationRef = useRef(null);
|
|
28
|
+
const [shouldRenderPreview, setShouldRenderPreview] = useState(
|
|
29
|
+
!deferUntilVisible || typeof globalThis.IntersectionObserver !== "function"
|
|
30
|
+
);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (shouldRenderPreview) return;
|
|
33
|
+
if (!deferUntilVisible) {
|
|
34
|
+
setShouldRenderPreview(true);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const target = reservationRef.current;
|
|
38
|
+
const Observer = globalThis.IntersectionObserver;
|
|
39
|
+
if (!target || typeof Observer !== "function") {
|
|
40
|
+
setShouldRenderPreview(true);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let fired = false;
|
|
44
|
+
const observer = new Observer(
|
|
45
|
+
(entries) => {
|
|
46
|
+
if (fired || !entries.some((entry) => entry.isIntersecting)) return;
|
|
47
|
+
fired = true;
|
|
48
|
+
observer.disconnect();
|
|
49
|
+
setShouldRenderPreview(true);
|
|
50
|
+
},
|
|
51
|
+
{ threshold: 0 }
|
|
52
|
+
);
|
|
53
|
+
observer.observe(target);
|
|
54
|
+
return () => {
|
|
55
|
+
fired = true;
|
|
56
|
+
observer.disconnect();
|
|
57
|
+
};
|
|
58
|
+
}, [deferUntilVisible, shouldRenderPreview]);
|
|
6
59
|
const {
|
|
60
|
+
[VISIBLE_MOUNT_PROP]: _visibleMount,
|
|
7
61
|
globalConfig,
|
|
8
62
|
html,
|
|
9
63
|
css,
|
|
10
64
|
head,
|
|
11
65
|
js,
|
|
12
66
|
title,
|
|
67
|
+
lang,
|
|
13
68
|
height,
|
|
14
69
|
defaultOpen,
|
|
70
|
+
labels,
|
|
71
|
+
showSource,
|
|
72
|
+
showViewportControls,
|
|
15
73
|
fullHeight,
|
|
16
74
|
sandbox,
|
|
17
75
|
externalStyles,
|
|
18
76
|
externalScripts,
|
|
19
77
|
preflight,
|
|
20
78
|
showResources
|
|
21
|
-
} =
|
|
79
|
+
} = runtimeProps;
|
|
80
|
+
if (!shouldRenderPreview) {
|
|
81
|
+
return /* @__PURE__ */ jsx(
|
|
82
|
+
HtmlPreviewReservation,
|
|
83
|
+
{
|
|
84
|
+
height,
|
|
85
|
+
reservationRef
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
22
89
|
const mergedHead = [globalConfig?.head, head].filter(Boolean).join("\n") || void 0;
|
|
23
90
|
const mergedCss = [globalConfig?.css, css].filter(Boolean).join("\n") || void 0;
|
|
24
91
|
const mergedJs = [globalConfig?.js, js].filter(Boolean).join("\n") || void 0;
|
|
@@ -30,8 +97,12 @@ function HtmlPreviewWrapperInner(props) {
|
|
|
30
97
|
head: mergedHead,
|
|
31
98
|
js: mergedJs,
|
|
32
99
|
title,
|
|
100
|
+
lang,
|
|
33
101
|
height,
|
|
34
102
|
defaultOpen,
|
|
103
|
+
labels,
|
|
104
|
+
showSource,
|
|
105
|
+
showViewportControls,
|
|
35
106
|
fullHeight,
|
|
36
107
|
sandbox,
|
|
37
108
|
componentCss: css,
|
|
@@ -46,9 +117,22 @@ function HtmlPreviewWrapperInner(props) {
|
|
|
46
117
|
}
|
|
47
118
|
HtmlPreviewWrapperInner.displayName = "HtmlPreviewWrapperInner";
|
|
48
119
|
function HtmlPreviewWrapper(props) {
|
|
120
|
+
const { loading = "eager", ...innerProps } = props;
|
|
121
|
+
if (loading === "visible") {
|
|
122
|
+
const visibleInnerProps = {
|
|
123
|
+
...innerProps,
|
|
124
|
+
[VISIBLE_MOUNT_PROP]: true
|
|
125
|
+
};
|
|
126
|
+
const rendered2 = Island({
|
|
127
|
+
when: "visible",
|
|
128
|
+
ssrFallback: /* @__PURE__ */ jsx(HtmlPreviewReservation, { height: innerProps.height }),
|
|
129
|
+
children: /* @__PURE__ */ jsx(HtmlPreviewWrapperInner, { ...visibleInnerProps })
|
|
130
|
+
});
|
|
131
|
+
return rendered2;
|
|
132
|
+
}
|
|
49
133
|
const rendered = Island({
|
|
50
134
|
when: "visible",
|
|
51
|
-
children: /* @__PURE__ */ jsx(HtmlPreviewWrapperInner, { ...
|
|
135
|
+
children: /* @__PURE__ */ jsx(HtmlPreviewWrapperInner, { ...innerProps })
|
|
52
136
|
});
|
|
53
137
|
return rendered;
|
|
54
138
|
}
|
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
/** @jsxRuntime automatic */
|
|
2
2
|
/** @jsxImportSource preact */
|
|
3
3
|
import type { VNode } from "preact";
|
|
4
|
+
import { type HtmlPreviewLabels } from "./preview-base.js";
|
|
5
|
+
export type { HtmlPreviewLabels } from "./preview-base.js";
|
|
4
6
|
export interface HtmlPreviewProps {
|
|
5
7
|
html: string;
|
|
6
8
|
css?: string;
|
|
7
9
|
head?: string;
|
|
8
10
|
js?: string;
|
|
9
11
|
title?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Language tag for the generated preview document's `<html lang>`
|
|
14
|
+
* attribute. Any nonblank tag is accepted; omission or a blank value falls
|
|
15
|
+
* back to `"en"`. Bound consumers should pass their active route locale.
|
|
16
|
+
*
|
|
17
|
+
* @default "en"
|
|
18
|
+
*/
|
|
19
|
+
lang?: string;
|
|
10
20
|
height?: number;
|
|
11
21
|
defaultOpen?: boolean;
|
|
22
|
+
/** Localized labels for viewport, source, and iframe controls. */
|
|
23
|
+
labels?: Partial<HtmlPreviewLabels>;
|
|
24
|
+
/** Whether the source toggle and code panel are rendered. @default true */
|
|
25
|
+
showSource?: boolean;
|
|
26
|
+
/** Whether the viewport preset controls are rendered. @default true */
|
|
27
|
+
showViewportControls?: boolean;
|
|
12
28
|
/**
|
|
13
29
|
* When true, injects `<style>html,body{height:100%}</style>` into the
|
|
14
30
|
* preview document so the preview's content can stretch to fill the
|
|
@@ -20,8 +36,9 @@ export interface HtmlPreviewProps {
|
|
|
20
36
|
* `iframe.contentDocument.body.scrollHeight` and resizes the iframe to
|
|
21
37
|
* fit; `fullHeight` makes the body's height derive FROM the iframe's own
|
|
22
38
|
* height instead, which creates a feedback loop when the iframe height is
|
|
23
|
-
* itself derived from the body.
|
|
24
|
-
*
|
|
39
|
+
* itself derived from the body. Auto-height is therefore disabled when
|
|
40
|
+
* `fullHeight` is used without a fixed height. Pair both props when the
|
|
41
|
+
* preview needs a height other than the default 200px reservation.
|
|
25
42
|
*
|
|
26
43
|
* @default false
|
|
27
44
|
*/
|
|
@@ -121,7 +138,15 @@ export declare function containsScript(head?: string, js?: string, externalScrip
|
|
|
121
138
|
* trust note.
|
|
122
139
|
*/
|
|
123
140
|
export declare function resolveSandbox(sandbox: string | undefined, hasScripts: boolean): string;
|
|
124
|
-
export
|
|
141
|
+
export interface BuildSrcdocMetadata {
|
|
142
|
+
/** Language for the generated document. Blank values fall back to `"en"`. */
|
|
143
|
+
lang?: string;
|
|
144
|
+
/** Visible preview title, used when the trusted head has no opening title. */
|
|
145
|
+
title?: string;
|
|
146
|
+
/** Localized preview label used after a blank visible title. */
|
|
147
|
+
previewLabel?: string;
|
|
148
|
+
}
|
|
149
|
+
export declare function buildSrcdoc(html: string, css?: string, head?: string, js?: string, fullHeight?: boolean, externalStyles?: string[], externalScripts?: string[], preflight?: boolean, metadata?: BuildSrcdocMetadata): string;
|
|
125
150
|
/**
|
|
126
151
|
* HTML preview widget — renders an isolated iframe with viewport
|
|
127
152
|
* controls and a collapsible code section.
|
|
@@ -134,4 +159,4 @@ export declare function buildSrcdoc(html: string, css?: string, head?: string, j
|
|
|
134
159
|
* Astro, or wire up the SSR-skip placeholder pattern for non-Astro
|
|
135
160
|
* consumers.
|
|
136
161
|
*/
|
|
137
|
-
export declare function HtmlPreview({ html, css, head, js, title, height, defaultOpen, fullHeight, sandbox, componentCss, componentHead, componentJs, externalStyles, externalScripts, preflight, showResources, }: HtmlPreviewProps): VNode;
|
|
162
|
+
export declare function HtmlPreview({ html, css, head, js, title, lang, height, defaultOpen, labels, showSource, showViewportControls, fullHeight, sandbox, componentCss, componentHead, componentJs, externalStyles, externalScripts, preflight, showResources, }: HtmlPreviewProps): VNode;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx } from "preact/jsx-runtime";
|
|
2
2
|
import { useMemo } from "preact/hooks";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PreviewBase
|
|
5
|
+
} from "./preview-base.js";
|
|
4
6
|
import { dedent } from "./dedent.js";
|
|
5
7
|
import { preflightCss } from "./preflight.js";
|
|
6
8
|
function containsScript(head, js, externalScripts) {
|
|
@@ -13,15 +15,49 @@ function resolveSandbox(sandbox, hasScripts) {
|
|
|
13
15
|
return sandbox ?? (hasScripts ? "allow-scripts allow-same-origin" : "allow-same-origin");
|
|
14
16
|
}
|
|
15
17
|
const fullHeightStyle = "<style>html,body{height:100%}</style>";
|
|
16
|
-
function
|
|
18
|
+
function nonBlank(value) {
|
|
19
|
+
return value?.trim() ? value : void 0;
|
|
20
|
+
}
|
|
21
|
+
function escapeHtmlAttribute(value) {
|
|
22
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<").replaceAll(">", ">");
|
|
23
|
+
}
|
|
24
|
+
function escapeHtmlText(value) {
|
|
25
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
26
|
+
}
|
|
27
|
+
function hasAuthorTitle(head) {
|
|
28
|
+
if (!head) return false;
|
|
29
|
+
const withoutComments = head.replace(/<!--[\s\S]*?-->/g, "");
|
|
30
|
+
const openingTitle = /<title(?=[\s>])/gi;
|
|
31
|
+
for (const match of withoutComments.matchAll(openingTitle)) {
|
|
32
|
+
let quote;
|
|
33
|
+
for (let index = (match.index ?? 0) + match[0].length; index < withoutComments.length; index += 1) {
|
|
34
|
+
const character = withoutComments[index];
|
|
35
|
+
if (quote) {
|
|
36
|
+
if (character === quote) quote = void 0;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (character === '"' || character === "'") {
|
|
40
|
+
quote = character;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (character === ">") return true;
|
|
44
|
+
if (character === "<") break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
function buildSrcdoc(html, css, head, js, fullHeight, externalStyles, externalScripts, preflight, metadata) {
|
|
17
50
|
const includePreflight = preflight ?? true;
|
|
51
|
+
const lang = nonBlank(metadata?.lang) ?? "en";
|
|
52
|
+
const generatedTitle = hasAuthorTitle(head) ? void 0 : nonBlank(metadata?.title) ?? nonBlank(metadata?.previewLabel) ?? "Preview";
|
|
18
53
|
const externalStylesHtml = (externalStyles ?? []).map((href) => `<link rel="stylesheet" href="${href}">`).join("\n");
|
|
19
54
|
const externalScriptsHtml = (externalScripts ?? []).map((src) => `<script src="${src}"></script>`).join("\n");
|
|
20
55
|
return `<!doctype html>
|
|
21
|
-
<html>
|
|
56
|
+
<html lang="${escapeHtmlAttribute(lang)}">
|
|
22
57
|
<head>
|
|
23
58
|
<meta charset="utf-8">
|
|
24
59
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
60
|
+
${generatedTitle ? `<title>${escapeHtmlText(generatedTitle)}</title>` : ""}
|
|
25
61
|
${includePreflight ? `<style>${preflightCss}</style>` : ""}
|
|
26
62
|
${fullHeight ? fullHeightStyle : ""}
|
|
27
63
|
${externalStylesHtml}
|
|
@@ -40,8 +76,12 @@ function HtmlPreview({
|
|
|
40
76
|
head,
|
|
41
77
|
js,
|
|
42
78
|
title,
|
|
79
|
+
lang,
|
|
43
80
|
height,
|
|
44
81
|
defaultOpen,
|
|
82
|
+
labels,
|
|
83
|
+
showSource,
|
|
84
|
+
showViewportControls,
|
|
45
85
|
fullHeight,
|
|
46
86
|
sandbox,
|
|
47
87
|
componentCss,
|
|
@@ -61,13 +101,27 @@ function HtmlPreview({
|
|
|
61
101
|
fullHeight,
|
|
62
102
|
externalStyles,
|
|
63
103
|
externalScripts,
|
|
64
|
-
preflight
|
|
104
|
+
preflight,
|
|
105
|
+
{ lang, title, previewLabel: labels?.preview }
|
|
65
106
|
),
|
|
66
|
-
[
|
|
107
|
+
[
|
|
108
|
+
html,
|
|
109
|
+
css,
|
|
110
|
+
head,
|
|
111
|
+
js,
|
|
112
|
+
fullHeight,
|
|
113
|
+
externalStyles,
|
|
114
|
+
externalScripts,
|
|
115
|
+
preflight,
|
|
116
|
+
lang,
|
|
117
|
+
title,
|
|
118
|
+
labels?.preview
|
|
119
|
+
]
|
|
67
120
|
);
|
|
68
121
|
const hasScripts = containsScript(head, js, externalScripts);
|
|
69
122
|
const syncDelay = hasScripts ? 300 : 0;
|
|
70
123
|
const sandboxValue = resolveSandbox(sandbox, hasScripts);
|
|
124
|
+
const sandboxAttributeValue = sandboxValue === "" ? " " : sandboxValue;
|
|
71
125
|
const codeBlocks = useMemo(() => {
|
|
72
126
|
const resourceLines = showResources ? [
|
|
73
127
|
...(externalStyles ?? []).map(
|
|
@@ -108,7 +162,11 @@ ${dedent(html)}` : dedent(html);
|
|
|
108
162
|
height,
|
|
109
163
|
srcdoc,
|
|
110
164
|
defaultOpen,
|
|
111
|
-
|
|
165
|
+
labels,
|
|
166
|
+
showSource,
|
|
167
|
+
showViewportControls,
|
|
168
|
+
autoHeight: height == null && !fullHeight,
|
|
169
|
+
sandbox: sandboxAttributeValue,
|
|
112
170
|
syncDelay,
|
|
113
171
|
codeBlocks
|
|
114
172
|
}
|
|
@@ -3,6 +3,6 @@ export type { HtmlPreviewWrapperProps, HtmlPreviewGlobalConfig, } from "./html-p
|
|
|
3
3
|
export { HtmlPreview } from "./html-preview.js";
|
|
4
4
|
export type { HtmlPreviewProps } from "./html-preview.js";
|
|
5
5
|
export { PreviewBase } from "./preview-base.js";
|
|
6
|
-
export type { PreviewBaseProps, CodeBlockData } from "./preview-base.js";
|
|
6
|
+
export type { PreviewBaseProps, CodeBlockData, HtmlPreviewLabels, } from "./preview-base.js";
|
|
7
7
|
export { HighlightedCode } from "./highlighted-code.js";
|
|
8
8
|
export type { HighlightedCodeProps } from "./highlighted-code.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface PreviewAutoHeightRuntime {
|
|
2
|
+
setTimeout(callback: () => void, delay: number): number;
|
|
3
|
+
clearTimeout(id: number): void;
|
|
4
|
+
requestAnimationFrame(callback: FrameRequestCallback): number;
|
|
5
|
+
cancelAnimationFrame(id: number): void;
|
|
6
|
+
}
|
|
7
|
+
export interface PreviewAutoHeightOptions {
|
|
8
|
+
iframe: HTMLIFrameElement;
|
|
9
|
+
syncDelay: number;
|
|
10
|
+
getCurrentHeight: () => number;
|
|
11
|
+
setHeight: (height: number) => void;
|
|
12
|
+
runtime?: PreviewAutoHeightRuntime;
|
|
13
|
+
}
|
|
14
|
+
export interface PreviewAutoHeightController {
|
|
15
|
+
handleLoad(): void;
|
|
16
|
+
schedule(): void;
|
|
17
|
+
destroy(): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Owns the readable-iframe auto-height lifecycle. Kept separate from Preact so
|
|
21
|
+
* reload generations, observer callbacks, and teardown remain deterministic.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createPreviewAutoHeightController({ iframe, syncDelay, getCurrentHeight, setHeight, runtime, }: PreviewAutoHeightOptions): PreviewAutoHeightController;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const MIN_PREVIEW_HEIGHT = 200;
|
|
2
|
+
const PREVIEW_HEIGHT_BUFFER = 16;
|
|
3
|
+
const SELF_SIZING_EPSILON = 1;
|
|
4
|
+
function defaultRuntime(iframe) {
|
|
5
|
+
const view = iframe.ownerDocument.defaultView ?? window;
|
|
6
|
+
return {
|
|
7
|
+
setTimeout: (callback, delay) => view.setTimeout(callback, delay),
|
|
8
|
+
clearTimeout: (id) => view.clearTimeout(id),
|
|
9
|
+
requestAnimationFrame: (callback) => view.requestAnimationFrame(callback),
|
|
10
|
+
cancelAnimationFrame: (id) => view.cancelAnimationFrame(id)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function createPreviewAutoHeightController({
|
|
14
|
+
iframe,
|
|
15
|
+
syncDelay,
|
|
16
|
+
getCurrentHeight,
|
|
17
|
+
setHeight,
|
|
18
|
+
runtime = defaultRuntime(iframe)
|
|
19
|
+
}) {
|
|
20
|
+
let generation = 0;
|
|
21
|
+
let document = null;
|
|
22
|
+
let observer = null;
|
|
23
|
+
let timeoutId = null;
|
|
24
|
+
let frameId = null;
|
|
25
|
+
let lastAppliedHeight = getCurrentHeight();
|
|
26
|
+
let lastWriteSample;
|
|
27
|
+
let destroyed = false;
|
|
28
|
+
const cancelPending = () => {
|
|
29
|
+
observer?.disconnect();
|
|
30
|
+
observer = null;
|
|
31
|
+
if (timeoutId != null) {
|
|
32
|
+
runtime.clearTimeout(timeoutId);
|
|
33
|
+
timeoutId = null;
|
|
34
|
+
}
|
|
35
|
+
if (frameId != null) {
|
|
36
|
+
runtime.cancelAnimationFrame(frameId);
|
|
37
|
+
frameId = null;
|
|
38
|
+
}
|
|
39
|
+
document = null;
|
|
40
|
+
};
|
|
41
|
+
const measure = (expectedGeneration) => {
|
|
42
|
+
if (destroyed || expectedGeneration !== generation || !document?.body) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const bodyHeight = document.body.scrollHeight;
|
|
46
|
+
if (!Number.isFinite(bodyHeight) || bodyHeight <= 0) return;
|
|
47
|
+
const currentHeight = getCurrentHeight();
|
|
48
|
+
const targetHeight = Math.max(
|
|
49
|
+
bodyHeight + PREVIEW_HEIGHT_BUFFER,
|
|
50
|
+
MIN_PREVIEW_HEIGHT
|
|
51
|
+
);
|
|
52
|
+
const isSelfSizingGrowth = targetHeight > currentHeight && Math.abs(bodyHeight - currentHeight) <= SELF_SIZING_EPSILON;
|
|
53
|
+
const followedPreviousWrite = targetHeight > currentHeight && lastWriteSample != null && Math.abs(currentHeight - lastWriteSample.targetHeight) <= SELF_SIZING_EPSILON && Math.abs(
|
|
54
|
+
bodyHeight - lastWriteSample.bodyHeight - (currentHeight - lastWriteSample.currentHeight)
|
|
55
|
+
) <= SELF_SIZING_EPSILON;
|
|
56
|
+
if (isSelfSizingGrowth || followedPreviousWrite || targetHeight === lastAppliedHeight) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
lastAppliedHeight = targetHeight;
|
|
60
|
+
lastWriteSample = { bodyHeight, currentHeight, targetHeight };
|
|
61
|
+
setHeight(targetHeight);
|
|
62
|
+
};
|
|
63
|
+
const scheduleFor = (expectedGeneration) => {
|
|
64
|
+
if (destroyed || expectedGeneration !== generation || document == null || frameId != null) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
frameId = runtime.requestAnimationFrame(() => {
|
|
68
|
+
frameId = null;
|
|
69
|
+
measure(expectedGeneration);
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const handleLoad = () => {
|
|
73
|
+
generation += 1;
|
|
74
|
+
cancelPending();
|
|
75
|
+
if (destroyed) return;
|
|
76
|
+
const expectedGeneration = generation;
|
|
77
|
+
lastAppliedHeight = getCurrentHeight();
|
|
78
|
+
lastWriteSample = void 0;
|
|
79
|
+
try {
|
|
80
|
+
const nextDocument = iframe.contentDocument;
|
|
81
|
+
if (!nextDocument?.body) return;
|
|
82
|
+
void nextDocument.body.scrollHeight;
|
|
83
|
+
document = nextDocument;
|
|
84
|
+
} catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
measure(expectedGeneration);
|
|
88
|
+
if (syncDelay > 0) {
|
|
89
|
+
timeoutId = runtime.setTimeout(() => {
|
|
90
|
+
timeoutId = null;
|
|
91
|
+
scheduleFor(expectedGeneration);
|
|
92
|
+
}, syncDelay);
|
|
93
|
+
}
|
|
94
|
+
const iframeWindow = iframe.contentWindow;
|
|
95
|
+
const ResizeObserverConstructor = iframeWindow?.ResizeObserver;
|
|
96
|
+
if (ResizeObserverConstructor) {
|
|
97
|
+
const nextObserver = new ResizeObserverConstructor(() => {
|
|
98
|
+
scheduleFor(expectedGeneration);
|
|
99
|
+
});
|
|
100
|
+
observer = nextObserver;
|
|
101
|
+
nextObserver.observe(document.body);
|
|
102
|
+
if (document.documentElement !== document.body) {
|
|
103
|
+
nextObserver.observe(document.documentElement);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
handleLoad,
|
|
109
|
+
schedule: () => scheduleFor(generation),
|
|
110
|
+
destroy: () => {
|
|
111
|
+
if (destroyed) return;
|
|
112
|
+
destroyed = true;
|
|
113
|
+
generation += 1;
|
|
114
|
+
cancelPending();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
createPreviewAutoHeightController
|
|
120
|
+
};
|