@deot/docs-markdown 1.0.10 → 1.1.1

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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Fragment, computed, createApp, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, h, inject, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, renderList, shallowRef, toDisplayString, unref, watch, withCtx, withDirectives } from "vue";
1
+ import { Fragment, computed, createApp, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, h, inject, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, shallowRef, toDisplayString, unref, watch, withDirectives } from "vue";
2
2
  import { provideLocale, useLocale } from "@deot/docs-locale";
3
3
  import Config from "markdown-it-chain";
4
4
  import anchor from "markdown-it-anchor";
@@ -6,30 +6,87 @@ import mdContainer from "markdown-it-container";
6
6
  import markdownIt from "markdown-it";
7
7
  import JSON5 from "json5";
8
8
  import { getScroller } from "@deot/helper-dom";
9
- import { Scroller } from "@deot/vc";
10
9
  //#region packages/markdown/src/markdown.ts
11
10
  var HTML_MD_SIGN = "md";
12
11
  var PLAYGROUND = "playground";
12
+ var TABS = "tabs";
13
13
  var TIP = "tip";
14
14
  var WARNING = "warning";
15
+ var CALLOUT_FILLED_RE = /(^|\s)filled(\s|$)/;
16
+ var TAB_MARKDOWN_INFO_RE = /^markdown(?:\s+(.*))?$/i;
17
+ var CALLOUT_INFO_ICON = [
18
+ "<svg width=\"22\" height=\"28\" viewBox=\"0 0 22 28\" fill=\"none\" aria-hidden=\"true\">",
19
+ "<circle class=\"docs-markdown-callout-icon__fill\" cx=\"11\" cy=\"14\" r=\"11\"></circle>",
20
+ "<circle class=\"docs-markdown-callout-icon__ring\" cx=\"11\" cy=\"14\" r=\"10.5\"></circle>",
21
+ "<path class=\"docs-markdown-callout-icon__mark\"",
22
+ " d=\"m12.5 19-1.011.337a1 1 0 0 1-1.253-1.3l1.528-4.074a1 1 0 0 0-1.253-1.3L9.5 13\"",
23
+ " stroke-width=\"1.5\" stroke-linecap=\"round\"></path>",
24
+ "<path class=\"docs-markdown-callout-icon__mark\" d=\"M12 9a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z\" stroke-width=\"1.5\"></path>",
25
+ "</svg>"
26
+ ].join("");
27
+ var CALLOUT_WARNING_ICON = [
28
+ "<svg width=\"22\" height=\"28\" viewBox=\"0 0 22 28\" fill=\"none\" aria-hidden=\"true\">",
29
+ "<circle class=\"docs-markdown-callout-icon__fill\" cx=\"11\" cy=\"14\" r=\"11\"></circle>",
30
+ "<circle class=\"docs-markdown-callout-icon__ring\" cx=\"11\" cy=\"14\" r=\"10.5\"></circle>",
31
+ "<path class=\"docs-markdown-callout-icon__mark\" d=\"M11 10v5.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>",
32
+ "<path class=\"docs-markdown-callout-icon__mark\" d=\"M11.5 18.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z\" stroke-width=\"1.5\"></path>",
33
+ "</svg>"
34
+ ].join("");
35
+ var renderCallout = (name, icon) => (tokens, idx) => {
36
+ if (tokens[idx].nesting === 1) return [
37
+ `<div class="${CALLOUT_FILLED_RE.test(tokens[idx].info) ? `${name} ${name}--filled` : name}" md>`,
38
+ "<span class=\"docs-markdown-callout-icon\" aria-hidden=\"true\">",
39
+ icon,
40
+ "</span><div class=\"docs-markdown-callout-body\">\n"
41
+ ].join("");
42
+ return "</div></div>\n";
43
+ };
15
44
  var config = new Config();
16
45
  config.options.html(true).typographer(true).linkify(true).end().plugin("anchor").use(anchor, [{ permalink: anchor.permalink.linkInsideHeader({
17
46
  placement: "before",
18
47
  symbol: "#"
19
48
  }) }]).end().plugin("container").use(($md) => {
20
- const reg = new RegExp(`^${PLAYGROUND}\\s*(.*)$`);
49
+ const playgroundReg = new RegExp(`^${PLAYGROUND}\\s*(.*)$`);
21
50
  $md.use(mdContainer, PLAYGROUND, { validate(params) {
22
- return params.trim().match(reg);
51
+ return params.trim().match(playgroundReg);
52
+ } });
53
+ const tabsReg = new RegExp(`^${TABS}\\s*$`);
54
+ $md.use(mdContainer, TABS, { validate(params) {
55
+ return tabsReg.test(params.trim());
23
56
  } });
24
- $md.use(mdContainer, TIP);
25
- $md.use(mdContainer, WARNING);
57
+ $md.use(mdContainer, TIP, { render: renderCallout(TIP, CALLOUT_INFO_ICON) });
58
+ $md.use(mdContainer, WARNING, { render: renderCallout(WARNING, CALLOUT_WARNING_ICON) });
26
59
  }).end();
27
60
  var md = config.toMd(markdownIt);
28
61
  var playgroundOpen = `container_${PLAYGROUND}_open`;
29
62
  var playgroundClose = `container_${PLAYGROUND}_close`;
63
+ var tabsOpen = `container_${TABS}_open`;
64
+ var tabsClose = `container_${TABS}_close`;
30
65
  var htmlCommentRE = /<!--([\s\S]*?)-->/g;
31
66
  var runtimeConfigRE = /<config\s+lang\s*=\s*["']json5["']\s*>([\s\S]*?)<\/config>/i;
32
67
  var renderPlaygroundError = (message) => `<div class="docs-playground-error">PLAYGROUND: ${md.utils.escapeHtml(message)}</div>\n`;
68
+ var renderTabsError = (message) => `<div class="docs-markdown-tabs-error">TABS: ${md.utils.escapeHtml(message)}</div>\n`;
69
+ /**
70
+ * 从 fence info 解析 Tab 显示名:去掉可选的 `markdown` 语言前缀。
71
+ * @param info fence 的 info 字符串,如 `markdown Linux` 或 `Android`。
72
+ * @returns 显示标题(可能为空)。
73
+ */
74
+ var parseTabFenceTitle = (info) => {
75
+ const trimmed = info.trim();
76
+ const match = trimmed.match(TAB_MARKDOWN_INFO_RE);
77
+ if (match) return (match[1] || "").trim();
78
+ return trimmed;
79
+ };
80
+ /**
81
+ * 生成 `?tab=` 使用的 id:小写、空白变 `-`;含非 ASCII 时与标题锚点一致做 encodeURIComponent。
82
+ * @param title Tab 显示名。
83
+ * @returns 可写入 query 的 id;标题为空时返回空串。
84
+ */
85
+ var toMarkdownTabId = (title) => {
86
+ const normalized = title.trim().toLowerCase().replace(/\s+/gu, "-");
87
+ if (!normalized) return "";
88
+ return /[^\x20-\x7E]/u.test(normalized) ? encodeURIComponent(normalized) : normalized;
89
+ };
33
90
  var PLAYGROUND_VIEWS = ["runtime", "files"];
34
91
  var isPlaygroundView = (value) => typeof value === "string" && PLAYGROUND_VIEWS.includes(value);
35
92
  var isRuntimeViewport = (viewport) => {
@@ -69,12 +126,22 @@ var validatePreviewInset = (propsData) => {
69
126
  if (Array.isArray(inset) && inset.length === 2 && inset.every(isPreviewInsetValue)) return "";
70
127
  return "previewInset 必须是非负数或 [垂直,水平] 非负数数组";
71
128
  };
72
- var validateExpand = (propsData) => {
73
- if (!("expand" in propsData)) return "";
74
- const expand = propsData.expand;
75
- if (expand === true) return "";
76
- if (typeof expand === "number" && Number.isFinite(expand) && expand > 0) return "";
77
- return "expand 必须是 true 或正数";
129
+ var validateExpandable = (propsData) => {
130
+ if (!("expandable" in propsData)) return "";
131
+ const expandable = propsData.expandable;
132
+ if (expandable === true) return "";
133
+ if (typeof expandable === "number" && Number.isFinite(expandable) && expandable > 0) return "";
134
+ return "expandable 必须是 true 或正数";
135
+ };
136
+ var validateTitle = (propsData) => {
137
+ if (!("title" in propsData)) return "";
138
+ if (typeof propsData.title === "string") return "";
139
+ return "title 必须是字符串";
140
+ };
141
+ var validateTitleId = (propsData) => {
142
+ if (!("id" in propsData)) return "";
143
+ if (typeof propsData.id === "string") return "";
144
+ return "id 必须是字符串";
78
145
  };
79
146
  var parseRuntimeProps = (tokens) => {
80
147
  for (const token of tokens) {
@@ -97,7 +164,61 @@ var parseRuntimeProps = (tokens) => {
97
164
  return {};
98
165
  };
99
166
  var renderPlaygroundAttrs = (propsData) => `data-props="${md.utils.escapeHtml(JSON.stringify(propsData))}"`;
100
- md.core.ruler.after("block", "runtime-files", (state) => {
167
+ var renderTabsHtml = (panels) => {
168
+ const parts = ["<div class=\"docs-markdown-tabs\" md data-tabs>", "<div class=\"docs-markdown-tabs__nav\" data-tabs-nav></div>"];
169
+ panels.forEach((panel, index) => {
170
+ const hidden = index === 0 ? "" : " hidden";
171
+ const id = md.utils.escapeHtml(panel.id);
172
+ const title = md.utils.escapeHtml(panel.title);
173
+ parts.push(`<div class="docs-markdown-tabs__panel" role="tabpanel" data-tab="${id}" data-tab-title="${title}"${hidden}>`, md.render(panel.content), "</div>");
174
+ });
175
+ parts.push("</div>\n");
176
+ return parts.join("");
177
+ };
178
+ md.core.ruler.after("block", "markdown-tabs", (state) => {
179
+ const panelSources = Array.isArray(state.env?.tabsPanelSources) ? state.env.tabsPanelSources : void 0;
180
+ for (let index = 0; index < state.tokens.length; index++) {
181
+ if (state.tokens[index].type !== tabsOpen) continue;
182
+ let depth = 1;
183
+ let closeIndex = index + 1;
184
+ for (; closeIndex < state.tokens.length; closeIndex++) {
185
+ if (state.tokens[closeIndex].type === tabsOpen) depth++;
186
+ if (state.tokens[closeIndex].type === tabsClose) depth--;
187
+ if (depth === 0) break;
188
+ }
189
+ const fences = state.tokens.slice(index + 1, closeIndex).filter((token) => token.type === "fence");
190
+ const placeholder = new state.Token("html_block", "", 0);
191
+ placeholder.block = true;
192
+ if (!fences.length) placeholder.content = renderTabsError("至少需要声明一个分栏");
193
+ else {
194
+ const panels = [];
195
+ const seen = /* @__PURE__ */ new Set();
196
+ let error = "";
197
+ for (const fence of fences) {
198
+ const title = parseTabFenceTitle(fence.info);
199
+ const id = toMarkdownTabId(title);
200
+ if (!title || !id) {
201
+ error = "每个分栏都必须声明标题";
202
+ break;
203
+ }
204
+ if (seen.has(id)) {
205
+ error = `分栏 ${title} 的 id 重复`;
206
+ break;
207
+ }
208
+ seen.add(id);
209
+ panels.push({
210
+ id,
211
+ title,
212
+ content: fence.content
213
+ });
214
+ panelSources?.push(fence.content);
215
+ }
216
+ placeholder.content = error ? renderTabsError(error) : renderTabsHtml(panels);
217
+ }
218
+ state.tokens.splice(index, closeIndex - index + 1, placeholder);
219
+ }
220
+ });
221
+ md.core.ruler.after("markdown-tabs", "runtime-files", (state) => {
101
222
  for (let index = 0; index < state.tokens.length; index++) {
102
223
  if (state.tokens[index].type !== playgroundOpen) continue;
103
224
  let depth = 1;
@@ -112,7 +233,7 @@ md.core.ruler.after("block", "runtime-files", (state) => {
112
233
  const placeholder = new state.Token("html_block", "", 0);
113
234
  placeholder.block = true;
114
235
  const propsData = parseRuntimeProps(innerTokens);
115
- const propsError = validateRuntimeViews(propsData) || validateRuntimeViewport(propsData) || validatePreviewInset(propsData) || validateExpand(propsData);
236
+ const propsError = validateRuntimeViews(propsData) || validateRuntimeViewport(propsData) || validatePreviewInset(propsData) || validateExpandable(propsData) || validateTitle(propsData) || validateTitleId(propsData);
116
237
  const propsAttr = renderPlaygroundAttrs(propsData);
117
238
  if (!fences.length) placeholder.content = renderPlaygroundError("至少需要声明一个代码文件");
118
239
  else if (propsError) placeholder.content = renderPlaygroundError(propsError);
@@ -150,7 +271,7 @@ md.core.ruler.after("block", "runtime-files", (state) => {
150
271
  });
151
272
  var renderAttrs = md.renderer.renderAttrs;
152
273
  md.renderer.renderAttrs = (token) => {
153
- const reg = new RegExp(`container_${PLAYGROUND}|fence|text`);
274
+ const reg = new RegExp(`container_${PLAYGROUND}|container_${TABS}|fence|text`);
154
275
  if (token.nesting !== -1 && token.type && !reg.test(token.type)) token.attrPush([HTML_MD_SIGN, ""]);
155
276
  return renderAttrs(token);
156
277
  };
@@ -178,32 +299,39 @@ var getInlineSearchText = (token) => {
178
299
  * @returns 文档标题、正文和按标题划分的小节。
179
300
  */
180
301
  var parseMarkdownSearchSections = (content) => {
181
- const tokens = md.parse(content, {});
182
302
  const sections = [];
183
303
  const documentParts = [];
184
304
  let activeSection;
185
- for (let index = 0; index < tokens.length; index++) {
186
- const token = tokens[index];
187
- if (token.type === "heading_open") {
188
- const inline = tokens[index + 1];
189
- if (inline?.type !== "inline") continue;
190
- const title = getInlineSearchText(inline);
191
- if (!title) continue;
192
- activeSection = {
193
- title,
194
- anchor: token.attrGet("id") || "",
195
- level: Number(token.tag.slice(1)) || 1,
196
- text: ""
197
- };
198
- sections.push(activeSection);
199
- continue;
200
- }
201
- if (token.type !== "inline" || tokens[index - 1]?.type === "heading_open") continue;
202
- const text = getInlineSearchText(token);
203
- if (!text) continue;
305
+ const consumeInline = (text) => {
306
+ if (!text) return;
204
307
  documentParts.push(text);
205
308
  if (activeSection) activeSection.text = normalizeSearchText(`${activeSection.text} ${text}`);
206
- }
309
+ };
310
+ const walk = (source, withHeadings) => {
311
+ const tabsPanelSources = [];
312
+ const tokens = md.parse(source, { tabsPanelSources });
313
+ for (let index = 0; index < tokens.length; index++) {
314
+ const token = tokens[index];
315
+ if (withHeadings && token.type === "heading_open") {
316
+ const inline = tokens[index + 1];
317
+ if (inline?.type !== "inline") continue;
318
+ const title = getInlineSearchText(inline);
319
+ if (!title) continue;
320
+ activeSection = {
321
+ title,
322
+ anchor: token.attrGet("id") || "",
323
+ level: Number(token.tag.slice(1)) || 1,
324
+ text: ""
325
+ };
326
+ sections.push(activeSection);
327
+ continue;
328
+ }
329
+ if (token.type !== "inline" || tokens[index - 1]?.type === "heading_open") continue;
330
+ consumeInline(getInlineSearchText(token));
331
+ }
332
+ for (const panelSource of tabsPanelSources) walk(panelSource, false);
333
+ };
334
+ walk(content, true);
207
335
  return {
208
336
  title: sections.find((section) => section.level === 1)?.title || sections[0]?.title || "",
209
337
  text: normalizeSearchText(documentParts.join(" ")),
@@ -211,11 +339,137 @@ var parseMarkdownSearchSections = (content) => {
211
339
  };
212
340
  };
213
341
  //#endregion
342
+ //#region packages/markdown/src/tab-query.ts
343
+ var markdownTabQueryKey = Symbol("docs-markdown-tab-query");
344
+ var TAB_QUERY = "tab";
345
+ /**
346
+ * 不依赖 Vue Router 的默认适配器:读写 `location.search` 的 `tab`,
347
+ * 并用 `history.replaceState` 更新(供 examples / 单测使用)。
348
+ * @returns 可注入 TabsNav 的 get/set/subscribe 适配器。
349
+ */
350
+ var createHistoryTabQueryAdapter = () => {
351
+ const read = () => {
352
+ try {
353
+ return new URLSearchParams(window.location.search).get(TAB_QUERY) || "";
354
+ } catch {
355
+ return "";
356
+ }
357
+ };
358
+ const write = (tab) => {
359
+ try {
360
+ const url = new URL(window.location.href);
361
+ if (tab) url.searchParams.set(TAB_QUERY, tab);
362
+ else url.searchParams.delete(TAB_QUERY);
363
+ window.history.replaceState(window.history.state, "", `${url.pathname}${url.search}${url.hash}`);
364
+ } catch {}
365
+ };
366
+ return {
367
+ get: read,
368
+ set: write,
369
+ subscribe: (listener) => {
370
+ const onPopState = () => listener();
371
+ window.addEventListener("popstate", onPopState);
372
+ return () => window.removeEventListener("popstate", onPopState);
373
+ }
374
+ };
375
+ };
376
+ //#endregion
377
+ //#region packages/markdown/src/tabs-nav.vue?vue&type=script&setup=true&lang.ts
378
+ var _hoisted_1$1 = ["aria-label"];
379
+ var _hoisted_2$1 = [
380
+ "id",
381
+ "aria-selected",
382
+ "aria-controls",
383
+ "tabindex",
384
+ "onClick"
385
+ ];
386
+ //#endregion
387
+ //#region packages/markdown/src/tabs-nav.vue
388
+ var tabs_nav_default = /* @__PURE__ */ defineComponent({
389
+ __name: "tabs-nav",
390
+ props: {
391
+ items: {},
392
+ label: {},
393
+ adapter: {},
394
+ root: {},
395
+ groupId: {}
396
+ },
397
+ setup(__props) {
398
+ const props = __props;
399
+ const activeId = ref("");
400
+ let unsubscribe;
401
+ const tabButtonId = (id) => `${props.groupId}-tab-${id}`;
402
+ const tabPanelId = (id) => `${props.groupId}-panel-${id}`;
403
+ const resolveActive = () => {
404
+ const fromQuery = props.adapter.get();
405
+ const matched = props.items.find((item) => item.id === fromQuery);
406
+ activeId.value = matched?.id || props.items[0]?.id || "";
407
+ };
408
+ const syncPanels = () => {
409
+ props.root.querySelectorAll(".docs-markdown-tabs__panel").forEach((panel) => {
410
+ const id = panel.dataset.tab || "";
411
+ const selected = id === activeId.value;
412
+ panel.hidden = !selected;
413
+ panel.setAttribute("aria-labelledby", tabButtonId(id));
414
+ if (selected) {
415
+ panel.removeAttribute("hidden");
416
+ panel.id = tabPanelId(id);
417
+ } else {
418
+ panel.setAttribute("hidden", "");
419
+ panel.id = tabPanelId(id);
420
+ }
421
+ });
422
+ };
423
+ const select = (id) => {
424
+ if (!id) return;
425
+ activeId.value = id;
426
+ props.adapter.set(id);
427
+ syncPanels();
428
+ };
429
+ onMounted(() => {
430
+ resolveActive();
431
+ syncPanels();
432
+ unsubscribe = props.adapter.subscribe?.(() => {
433
+ resolveActive();
434
+ syncPanels();
435
+ });
436
+ });
437
+ onBeforeUnmount(() => {
438
+ unsubscribe?.();
439
+ unsubscribe = void 0;
440
+ });
441
+ watch(() => props.items.map((item) => item.id).join("\0"), () => {
442
+ resolveActive();
443
+ syncPanels();
444
+ });
445
+ return (_ctx, _cache) => {
446
+ return openBlock(), createElementBlock("div", {
447
+ class: "docs-markdown-tabs__list",
448
+ role: "tablist",
449
+ "aria-label": __props.label
450
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
451
+ return openBlock(), createElementBlock("button", {
452
+ key: item.id,
453
+ type: "button",
454
+ class: normalizeClass(["docs-markdown-tabs__tab", { "docs-markdown-tabs__tab--active": item.id === activeId.value }]),
455
+ role: "tab",
456
+ id: tabButtonId(item.id),
457
+ "aria-selected": item.id === activeId.value,
458
+ "aria-controls": tabPanelId(item.id),
459
+ tabindex: item.id === activeId.value ? 0 : -1,
460
+ onClick: ($event) => select(item.id)
461
+ }, toDisplayString(item.title), 11, _hoisted_2$1);
462
+ }), 128))], 8, _hoisted_1$1);
463
+ };
464
+ }
465
+ });
466
+ //#endregion
214
467
  //#region packages/markdown/src/directive.ts
215
468
  var mountedApps = /* @__PURE__ */ new WeakMap();
216
469
  var renderVersions = /* @__PURE__ */ new WeakMap();
217
470
  var playgroundModule;
218
471
  var loadPlayground = () => playgroundModule ||= import("@deot/docs-playground");
472
+ var defaultTabQuery = createHistoryTabQueryAdapter();
219
473
  var cleanup = (el) => {
220
474
  for (const app of mountedApps.get(el) || []) app.unmount();
221
475
  mountedApps.delete(el);
@@ -223,12 +477,37 @@ var cleanup = (el) => {
223
477
  var resolveBlockLanguage = (block) => {
224
478
  return [...block.classList].find((className) => className.startsWith("language-"))?.slice(9) || "";
225
479
  };
480
+ var serializePlaygroundDefaults = (value) => JSON.stringify(value || {});
481
+ var mountTabs = (el, apps, locale, adapter) => {
482
+ const label = locale.markdown?.tabs?.label || "Tabs";
483
+ el.querySelectorAll("[data-tabs]").forEach((root, index) => {
484
+ const nav = root.querySelector("[data-tabs-nav]");
485
+ if (!nav) return;
486
+ const items = [...root.querySelectorAll(".docs-markdown-tabs__panel")].map((panel) => ({
487
+ id: panel.dataset.tab || "",
488
+ title: panel.dataset.tabTitle || panel.dataset.tab || ""
489
+ })).filter((item) => item.id && item.title);
490
+ if (!items.length) return;
491
+ const groupId = `docs-markdown-tabs-${index}`;
492
+ const app = createApp({ render: () => h(tabs_nav_default, {
493
+ items,
494
+ label,
495
+ adapter,
496
+ root,
497
+ groupId
498
+ }) });
499
+ app.mount(nav);
500
+ apps.push(app);
501
+ });
502
+ };
226
503
  var render = async (el, binding) => {
227
504
  cleanup(el);
228
505
  const version = (renderVersions.get(el) || 0) + 1;
229
506
  renderVersions.set(el, version);
230
507
  el.innerHTML = binding.value.source ? Markdown.render(binding.value.source) : "";
231
508
  const apps = [];
509
+ const adapter = binding.value.tabQuery || defaultTabQuery;
510
+ mountTabs(el, apps, binding.value.locale, adapter);
232
511
  const blocks = el.querySelectorAll("pre code");
233
512
  const playgrounds = el.querySelectorAll("div[data-playground]");
234
513
  if (!blocks.length && !playgrounds.length) {
@@ -251,6 +530,7 @@ var render = async (el, binding) => {
251
530
  app.mount(mountPoint);
252
531
  apps.push(app);
253
532
  });
533
+ const sitePlaygroundDefaults = binding.value.playground && typeof binding.value.playground === "object" ? binding.value.playground : {};
254
534
  playgrounds.forEach((item) => {
255
535
  const code = item.dataset.code;
256
536
  let files;
@@ -266,6 +546,7 @@ var render = async (el, binding) => {
266
546
  entry: item.dataset.entry
267
547
  } : { modelValue: code };
268
548
  const app = createApp(() => h(DocsPlayground.Playground, {
549
+ ...sitePlaygroundDefaults,
269
550
  ...typeof propsData === "object" ? propsData : {},
270
551
  ...runtimeProps,
271
552
  locale: binding.value.locale
@@ -276,7 +557,7 @@ var render = async (el, binding) => {
276
557
  mountedApps.set(el, apps);
277
558
  };
278
559
  var update = (el, binding) => {
279
- if (binding.value.source !== binding.oldValue?.source || binding.value.locale !== binding.oldValue?.locale) render(el, binding);
560
+ if (binding.value.source !== binding.oldValue?.source || binding.value.locale !== binding.oldValue?.locale || binding.value.tabQuery !== binding.oldValue?.tabQuery || serializePlaygroundDefaults(binding.value.playground) !== serializePlaygroundDefaults(binding.oldValue?.playground)) render(el, binding);
280
561
  };
281
562
  var vMarkdown = {
282
563
  mounted: (el, binding) => void render(el, binding),
@@ -286,17 +567,154 @@ var vMarkdown = {
286
567
  cleanup(el);
287
568
  }
288
569
  };
570
+ /**
571
+ * 指示器高度不能超过正文实际高度,也不能超出当前滚动容器可视区域。
572
+ * 与文档壳大纲的封顶规则一致。
573
+ * @param articleHeight 正文高度。
574
+ * @param viewportHeight 主滚动容器可视高度。
575
+ * @returns 指示器最大高度,单位 px。
576
+ */
577
+ var capIndicatorMaxHeight = (articleHeight, viewportHeight) => {
578
+ const article = Math.max(0, articleHeight);
579
+ const viewport = Math.max(0, viewportHeight);
580
+ if (!article) return viewport;
581
+ if (!viewport) return article;
582
+ return Math.min(article, viewport);
583
+ };
584
+ /**
585
+ * 滑轨高度再受 sticky 父级剩余空间约束,避免滚到文末盖住 footer。
586
+ * @param cap 正文/视口封顶后的高度。
587
+ * @param available 指示器顶部到父级底部的剩余像素;非有限值表示不额外约束。
588
+ * @returns 最终高度,单位 px。
589
+ */
590
+ var capStickyAvailableHeight = (cap, available) => {
591
+ const limited = Math.max(0, cap);
592
+ if (!Number.isFinite(available)) return limited;
593
+ return Math.min(limited, Math.max(0, available));
594
+ };
595
+ /**
596
+ * 把主滚动位置映射成小地图可视窗口(相对轨道的 0–1 比例)。
597
+ * @param scrollTop 当前滚动偏移。
598
+ * @param clientHeight 滚动容器可视高度。
599
+ * @param scrollHeight 滚动内容总高度。
600
+ * @returns 窗口顶部与高度比例。
601
+ */
602
+ var getMinimapWindow = (scrollTop, clientHeight, scrollHeight) => {
603
+ const total = Math.max(0, scrollHeight);
604
+ if (!total) return {
605
+ height: 1,
606
+ top: 0
607
+ };
608
+ const height = Math.min(1, Math.max(0, clientHeight / total));
609
+ const maxTop = Math.max(0, 1 - height);
610
+ return {
611
+ height,
612
+ top: maxTop <= 0 ? 0 : Math.min(maxTop, Math.max(0, scrollTop / total))
613
+ };
614
+ };
615
+ /**
616
+ * 按小地图窗口顶部选出当前阅读刻度:最后一个仍不晚于窗口顶的块。
617
+ * 滚到末屏时固定最后一条,避免末块永远到不了窗口顶。
618
+ * @param ratios 刻度在正文中的 0–1 比例,按出现顺序。
619
+ * @param windowTop 可视窗口顶部比例。
620
+ * @param windowHeight 可视窗口高度比例。
621
+ * @returns 当前刻度序号;没有刻度时为 -1。
622
+ */
623
+ var findActiveMinimapIndex = (ratios, windowTop, windowHeight) => {
624
+ if (!ratios.length) return -1;
625
+ if (windowTop + windowHeight >= .999) return ratios.length - 1;
626
+ let active = 0;
627
+ for (let index = 0; index < ratios.length; index++) if ((ratios[index] || 0) <= windowTop) active = index;
628
+ return active;
629
+ };
630
+ /**
631
+ * 判断刻度是否落在当前可视窗口内。
632
+ * @param ratio 刻度比例。
633
+ * @param windowTop 可视窗口顶部比例。
634
+ * @param windowHeight 可视窗口高度比例。
635
+ * @returns 是否位于窗口内。
636
+ */
637
+ var isMinimapMarkerInView = (ratio, windowTop, windowHeight) => {
638
+ const top = Math.max(0, windowTop);
639
+ const bottom = top + Math.max(0, windowHeight);
640
+ return ratio >= top && ratio <= bottom;
641
+ };
642
+ /**
643
+ * 把指针纵坐标映射成轨道上的 0–1 比例。
644
+ * @param clientY 指针视口纵坐标。
645
+ * @param trackTop 轨道顶部。
646
+ * @param trackHeight 轨道高度。
647
+ * @returns 夹紧后的比例。
648
+ */
649
+ var getMinimapPointerRatio = (clientY, trackTop, trackHeight) => {
650
+ if (trackHeight <= 0) return 0;
651
+ return Math.min(1, Math.max(0, (clientY - trackTop) / trackHeight));
652
+ };
653
+ /**
654
+ * 把指示器刻度对齐到文档壳斜纹轨的内侧,并留出 `INDICATOR_RAIL_GAP`。
655
+ * 左侧靠近 `rail--start` 的右缘,右侧靠近 `rail--end` 的左缘。
656
+ * 没有可见轨道时返回 undefined,由样式回退到正文边缘。
657
+ * @param root 指示器根节点相对视口的盒子。
658
+ * @param rail 当前侧斜纹轨的盒子。
659
+ * @param side 指示器所在侧。
660
+ * @param viewportWidth 刻度轨道宽度。
661
+ * @returns 相对指示器根节点的 left 偏移,单位 px。
662
+ */
663
+ var getIndicatorInlineOffset = (root, rail, side, viewportWidth) => {
664
+ if (!rail || rail.width <= 0 || rail.height <= 0) return;
665
+ if (side === "left") return rail.right - root.left + 8;
666
+ return rail.left - root.left - viewportWidth - 8;
667
+ };
668
+ //#endregion
669
+ //#region packages/markdown/src/headings.ts
670
+ var HEADING_SELECTOR = "h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]";
671
+ var SKIP_CLOSEST = [
672
+ "pre",
673
+ ".docs-markdown-code-preview",
674
+ "[data-playground][data-code]",
675
+ "[data-playground][data-files]",
676
+ ".docs-markdown-tabs__panel[hidden]"
677
+ ].join(", ");
678
+ /**
679
+ * 去掉 markdown-it-anchor 的井号后,读取标题的可见文案。
680
+ * @param element 当前标题节点。
681
+ * @returns 单行章节标题。
682
+ */
683
+ var getMarkdownHeadingTitle = (element) => {
684
+ const clone = element.cloneNode(true);
685
+ clone.querySelectorAll(".header-anchor").forEach((node) => node.remove());
686
+ return (clone.textContent || "").replace(/\s+/g, " ").trim();
687
+ };
688
+ /**
689
+ * 从已渲染的 Markdown 中收集带 id 的标题,规则与文档壳大纲一致:
690
+ * 忽略代码块 / Playground 内部标题;有章节标题时不把页面 h1 算进去。
691
+ * @param root Markdown 所在容器。
692
+ * @returns 大纲条目,按出现顺序排列。
693
+ */
694
+ var collectMarkdownHeadings = (root) => {
695
+ if (!root || !("querySelectorAll" in root)) return [];
696
+ return [...root.querySelectorAll(HEADING_SELECTOR)].filter((element) => !element.closest(SKIP_CLOSEST) && Boolean(getMarkdownHeadingTitle(element))).map((element) => ({
697
+ element,
698
+ id: element.id,
699
+ level: Number(element.tagName.slice(1)),
700
+ text: getMarkdownHeadingTitle(element)
701
+ })).filter((item, _, headings) => item.level > 1 || headings.every((heading) => heading.level === 1));
702
+ };
289
703
  //#endregion
290
704
  //#region packages/markdown/src/indicator.vue?vue&type=script&setup=true&lang.ts
291
- var _hoisted_1$1 = ["aria-label"];
292
- var _hoisted_2 = { class: "docs-markdown-indicator__list" };
293
- var _hoisted_3 = ["aria-label", "onClick"];
294
- var _hoisted_4 = { class: "docs-markdown-indicator__preview-title" };
295
- var _hoisted_5 = {
705
+ var _hoisted_1 = ["aria-label"];
706
+ var _hoisted_2 = ["aria-valuenow", "aria-valuetext"];
707
+ var _hoisted_3 = { class: "docs-markdown-indicator__preview-title" };
708
+ var _hoisted_4 = {
296
709
  key: 0,
297
710
  class: "docs-markdown-indicator__preview-content"
298
711
  };
299
- var MAX_ARIA_LABEL_LENGTH = 180;
712
+ var LAYOUT_SELECTOR = ".docs-layout";
713
+ var RAIL_START_SELECTOR = ".docs-layout__rail--start";
714
+ var RAIL_END_SELECTOR = ".docs-layout__rail--end";
715
+ var VIEWPORT_WIDTH = 40;
716
+ /** 与 docs-page-outline 上下 padding 一致,需从轨道高度中扣除。 */
717
+ var INDICATOR_CHROME = 136;
300
718
  //#endregion
301
719
  //#region packages/markdown/src/indicator.vue
302
720
  var indicator_default = /* @__PURE__ */ defineComponent({
@@ -306,61 +724,82 @@ var indicator_default = /* @__PURE__ */ defineComponent({
306
724
  target: {}
307
725
  },
308
726
  setup(__props) {
309
- const BLOCK_SELECTOR = [
310
- "h1",
311
- "h2",
312
- "h3",
313
- "h4",
314
- "h5",
315
- "h6",
316
- "p",
317
- "li",
318
- "pre",
319
- "blockquote",
320
- "table",
321
- ".tip",
322
- ".warning",
323
- ".docs-markdown-code-preview",
324
- "[data-playground]"
325
- ].join(",");
326
- const CONTAINER_SELECTOR = [
327
- "li",
328
- "blockquote",
329
- "table",
330
- ".tip",
331
- ".warning",
332
- ".docs-markdown-code-preview",
333
- "[data-playground]"
334
- ].join(",");
335
727
  const props = __props;
336
728
  const { lang: localeName, t } = useLocale();
337
729
  const parentScroller = inject("vc-scroller", null);
338
730
  const indicatorRoot = ref();
339
731
  const viewport = ref();
340
732
  const rail = ref();
341
- const indicatorScroller = ref();
342
733
  const markers = shallowRef([]);
343
- const activeIndex = ref(0);
344
734
  const hoverIndex = ref();
735
+ const activeIndex = ref(-1);
345
736
  const previewTop = ref(0);
346
737
  const dragging = ref(false);
347
- const stickyInset = ref("0px");
738
+ const cappedHeight = ref(0);
739
+ const inlineAnchor = ref();
740
+ const windowBox = ref(getMinimapWindow(0, 0, 0));
348
741
  let observer;
349
742
  let hostResizeObserver;
350
743
  let scrollHost;
351
744
  let usesParentScroller = false;
352
745
  let captureTarget;
353
746
  let refreshFrame = 0;
354
- let activeFrame = 0;
747
+ let windowFrame = 0;
355
748
  let targetGeneration = 0;
356
749
  const toCssLength = (value, fallback) => typeof value === "number" ? `${value}px` : value || fallback;
750
+ const resolvedHeight = computed(() => {
751
+ const cap = cappedHeight.value;
752
+ const configured = props.options.height;
753
+ if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) return `${cap > 0 ? Math.min(configured, cap) : configured}px`;
754
+ if (typeof configured === "string" && configured.trim()) return configured;
755
+ if (cap > 0) return `${cap}px`;
756
+ return "calc(100svh - 60px)";
757
+ });
758
+ /** 扣除上下 padding 后的轨道高度。小地图轨高等于该值,不再内部滚动。 */
759
+ const resolvedTrackHeight = computed(() => {
760
+ const height = resolvedHeight.value;
761
+ if (height.endsWith("px")) {
762
+ const total = Number.parseFloat(height);
763
+ if (Number.isFinite(total)) return `${Math.max(0, total - INDICATOR_CHROME)}px`;
764
+ }
765
+ return `calc(${height} - ${INDICATOR_CHROME}px)`;
766
+ });
357
767
  const rootStyle = computed(() => ({
358
- "--docs-markdown-indicator-height": toCssLength(props.options.height, "min(72vh, 600px)"),
359
- "--docs-markdown-indicator-inset": stickyInset.value,
768
+ "--docs-markdown-indicator-height": resolvedHeight.value,
769
+ "--docs-markdown-indicator-track-height": resolvedTrackHeight.value,
360
770
  "--docs-markdown-indicator-top": toCssLength(props.options.top, "0px")
361
771
  }));
362
772
  const hoveredMarker = computed(() => typeof hoverIndex.value === "number" ? markers.value[hoverIndex.value] : void 0);
363
- const previewStyle = computed(() => ({ top: `${previewTop.value}px` }));
773
+ const isMarkerInView = (index) => {
774
+ const marker = markers.value[index];
775
+ if (!marker) return false;
776
+ return isMinimapMarkerInView(marker.ratio, windowBox.value.top, windowBox.value.height);
777
+ };
778
+ const resolvedPosition = computed(() => props.options.position === "right" ? "right" : "left");
779
+ const scrollProgress = computed(() => Math.round(windowBox.value.top * 100));
780
+ const viewportStyle = computed(() => {
781
+ if (typeof inlineAnchor.value !== "number") return;
782
+ return {
783
+ left: `${inlineAnchor.value}px`,
784
+ right: "auto"
785
+ };
786
+ });
787
+ const windowStyle = computed(() => ({
788
+ height: `${windowBox.value.height * 100}%`,
789
+ top: `${windowBox.value.top * 100}%`
790
+ }));
791
+ const previewStyle = computed(() => {
792
+ const style = { top: `${previewTop.value}px` };
793
+ if (typeof inlineAnchor.value !== "number") return style;
794
+ if (resolvedPosition.value === "left") {
795
+ style.left = `${inlineAnchor.value + 24}px`;
796
+ style.right = "auto";
797
+ } else {
798
+ style.left = "auto";
799
+ style.right = `calc(100% - ${inlineAnchor.value}px)`;
800
+ }
801
+ return style;
802
+ });
364
803
  /**
365
804
  * 保留文档块的有效换行,同时折叠每行内部的多余空白。
366
805
  * @param element 当前文档块。
@@ -368,30 +807,21 @@ var indicator_default = /* @__PURE__ */ defineComponent({
368
807
  */
369
808
  const getBlockText = (element) => (element.textContent || "").split(/\n+/).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
370
809
  /**
371
- * 清理标题锚点产生的井号,并提供无标题内容的兜底文本。
372
- * @param element 当前标题节点。
373
- * @returns 单行章节标题。
810
+ * 取当前标题到下一标题之间的第一段正文,作为悬停摘要。
811
+ * @param heading 当前标题。
812
+ * @param nextHeading 下一个标题,没有则为空。
813
+ * @returns 摘要文本。
374
814
  */
375
- const getHeadingTitle = (element) => getBlockText(element).replace(/^#\s*/, "").replace(/\s+/g, " ").trim() || t("markdown.indicator.untitled");
376
- /**
377
- * 为无障碍名称限制长度,避免长代码块生成过大的属性值。
378
- * @param title 当前章节标题。
379
- * @param content 当前文档块内容。
380
- * @returns 标题和内容组成的简短名称。
381
- */
382
- const getAriaLabel = (title, content) => {
383
- const value = content ? `${title}: ${content.replace(/\s+/g, " ")}` : title;
384
- if (value.length <= MAX_ARIA_LABEL_LENGTH) return value;
385
- return `${value.slice(0, MAX_ARIA_LABEL_LENGTH).trim()}…`;
386
- };
387
- /**
388
- * 判断候选节点是否已经包含在另一个可独立定位的文档块内。
389
- * @param element 当前候选节点。
390
- * @returns 是否应由外层文档块统一表示。
391
- */
392
- const isNestedBlock = (element) => {
393
- const container = element.closest(CONTAINER_SELECTOR);
394
- return Boolean(container && container !== element);
815
+ const getPreviewContent = (heading, nextHeading) => {
816
+ let node = heading.nextElementSibling;
817
+ while (node && node !== nextHeading) {
818
+ if (!/^H[1-6]$/.test(node.tagName)) {
819
+ const text = getBlockText(node);
820
+ if (text) return text;
821
+ }
822
+ node = node.nextElementSibling;
823
+ }
824
+ return "";
395
825
  };
396
826
  /**
397
827
  * 获取 Markdown 使用的原生滚动宿主。
@@ -400,77 +830,118 @@ var indicator_default = /* @__PURE__ */ defineComponent({
400
830
  */
401
831
  const getScrollHost = () => getScroller(props.target) || window;
402
832
  /**
403
- * 获取当前滚动宿主的可视区域顶部。
404
- * @returns 相对于视口的顶部坐标。
833
+ * 读取当前滚动容器的可视高度。
834
+ * @returns 滚动容器 clientHeight,窗口滚动时回退到 innerHeight。
405
835
  */
406
- const getScrollHostTop = () => {
407
- if (usesParentScroller) return parentScroller?.wrapper?.getBoundingClientRect().top || 0;
408
- return scrollHost instanceof HTMLElement ? scrollHost.getBoundingClientRect().top : 0;
836
+ const getScrollHostClientHeight = () => {
837
+ if (usesParentScroller) return parentScroller?.wrapper?.clientHeight || parentScroller?.clientHeight || window.innerHeight;
838
+ if (scrollHost instanceof HTMLElement) return scrollHost.clientHeight;
839
+ return window.innerHeight;
409
840
  };
410
841
  /**
411
- * 判断文档是否已经到达滚动末尾。
412
- * 末屏通常无法把最后一个内容块推到顶部阈值,因此需要显式选中最后一条刻度。
413
- * @returns 是否位于可滚动内容末尾。
842
+ * 读取主滚动的位置与尺寸,供可视窗口和 scrub 共用。
843
+ * @returns 当前滚动偏移、可视高度和内容总高度。
414
844
  */
415
- const isScrollEnd = () => {
416
- let clientHeight;
417
- let scrollHeight;
418
- let scrollTop;
419
- if (usesParentScroller) {
420
- clientHeight = parentScroller?.clientHeight || 0;
421
- scrollHeight = parentScroller?.scrollHeight || 0;
422
- scrollTop = parentScroller?.scrollTop || 0;
423
- } else if (scrollHost instanceof HTMLElement) {
424
- clientHeight = scrollHost.clientHeight;
425
- scrollHeight = scrollHost.scrollHeight;
426
- scrollTop = scrollHost.scrollTop;
427
- } else {
428
- clientHeight = window.innerHeight;
429
- scrollHeight = document.documentElement.scrollHeight;
430
- scrollTop = window.scrollY;
431
- }
432
- return scrollHeight > clientHeight && scrollTop + clientHeight >= scrollHeight - 1;
845
+ const getScrollMetrics = () => {
846
+ if (usesParentScroller) return {
847
+ clientHeight: parentScroller?.clientHeight || parentScroller?.wrapper?.clientHeight || 0,
848
+ scrollHeight: parentScroller?.scrollHeight || parentScroller?.wrapper?.scrollHeight || 0,
849
+ scrollTop: parentScroller?.scrollTop || 0
850
+ };
851
+ if (scrollHost instanceof HTMLElement) return {
852
+ clientHeight: scrollHost.clientHeight,
853
+ scrollHeight: scrollHost.scrollHeight,
854
+ scrollTop: scrollHost.scrollTop
855
+ };
856
+ return {
857
+ clientHeight: window.innerHeight,
858
+ scrollHeight: document.documentElement.scrollHeight,
859
+ scrollTop: window.scrollY
860
+ };
433
861
  };
434
862
  /**
435
- * 保证当前阅读刻度处于内部 Scroller 的可视范围内。
436
- * @param index 当前阅读刻度序号。
863
+ * 按正文、主滚动可视高度,以及 sticky 父级剩余空间封顶,避免盖住 footer。
864
+ * @returns 指示器可用的最大高度,单位 px。
437
865
  */
438
- const followActiveMarker = async (index) => {
439
- await nextTick();
440
- const marker = rail.value?.querySelectorAll(".docs-markdown-indicator__marker")[index];
441
- const wrapper = viewport.value?.querySelector(".vc-scroller__wrapper");
442
- if (!marker || !wrapper) return;
443
- const markerRect = marker.getBoundingClientRect();
444
- const wrapperRect = wrapper.getBoundingClientRect();
445
- const padding = 16;
446
- if (markerRect.top >= wrapperRect.top + padding && markerRect.bottom <= wrapperRect.bottom - padding) return;
447
- const markerCenter = markerRect.top + markerRect.height / 2;
448
- const wrapperCenter = wrapperRect.top + wrapperRect.height / 2;
449
- indicatorScroller.value?.setScrollTop(Math.max(0, wrapper.scrollTop + markerCenter - wrapperCenter));
866
+ const getStickyAvailableHeight = () => {
867
+ const root = indicatorRoot.value;
868
+ const parent = root?.parentElement;
869
+ if (!root || !parent) return Number.POSITIVE_INFINITY;
870
+ const parentBox = parent.getBoundingClientRect();
871
+ if (parentBox.height <= 0 && parentBox.bottom === 0) return Number.POSITIVE_INFINITY;
872
+ return parentBox.bottom - root.getBoundingClientRect().top;
450
873
  };
451
- /** 根据滚动位置更新当前阅读块,不触发 Markdown 内容重渲染。 */
452
- const updateActiveIndex = () => {
453
- activeFrame = 0;
454
- if (!markers.value.length) return;
455
- const threshold = getScrollHostTop() + 80;
456
- let nextIndex = markers.value.length - 1;
457
- if (!isScrollEnd()) {
458
- nextIndex = 0;
459
- for (let index = 0; index < markers.value.length; index++) {
460
- if (markers.value[index].element.getBoundingClientRect().top > threshold) break;
461
- nextIndex = index;
462
- }
874
+ const updateCappedHeight = () => {
875
+ cappedHeight.value = capStickyAvailableHeight(capIndicatorMaxHeight(props.target?.offsetHeight || 0, getScrollHostClientHeight()), getStickyAvailableHeight());
876
+ };
877
+ /** 根据主滚动位置更新小地图可视窗口和当前刻度。 */
878
+ const updateWindow = () => {
879
+ windowFrame = 0;
880
+ const metrics = getScrollMetrics();
881
+ windowBox.value = getMinimapWindow(metrics.scrollTop, metrics.clientHeight, metrics.scrollHeight);
882
+ activeIndex.value = findActiveMinimapIndex(markers.value.map((marker) => marker.ratio), windowBox.value.top, windowBox.value.height);
883
+ };
884
+ /**
885
+ * 有文档壳斜纹轨时,把刻度靠近轨道内侧并留出间距;否则清空偏移,沿用正文边缘定位。
886
+ */
887
+ const updateInlineAnchor = () => {
888
+ const root = indicatorRoot.value;
889
+ if (!root) {
890
+ inlineAnchor.value = void 0;
891
+ return;
463
892
  }
464
- if (activeIndex.value !== nextIndex) {
465
- activeIndex.value = nextIndex;
466
- followActiveMarker(nextIndex);
893
+ const layoutRail = root.closest(LAYOUT_SELECTOR)?.querySelector(resolvedPosition.value === "left" ? RAIL_START_SELECTOR : RAIL_END_SELECTOR);
894
+ const railRect = layoutRail && getComputedStyle(layoutRail).display !== "none" ? layoutRail.getBoundingClientRect() : void 0;
895
+ inlineAnchor.value = getIndicatorInlineOffset(root.getBoundingClientRect(), railRect, resolvedPosition.value, viewport.value?.offsetWidth || VIEWPORT_WIDTH);
896
+ };
897
+ /** 同步高度封顶、斜纹轨锚点、刻度比例和可视窗口。 */
898
+ const updateLayout = () => {
899
+ updateCappedHeight();
900
+ updateInlineAnchor();
901
+ if (props.target && markers.value.length) {
902
+ const article = props.target;
903
+ markers.value = markers.value.map((marker) => ({
904
+ ...marker,
905
+ ratio: getBlockRatio(marker.element, article)
906
+ }));
467
907
  }
908
+ updateWindow();
468
909
  };
469
- /** 将连续滚动事件合并到浏览器的下一绘制帧。 */
470
- const handleScroll = () => {
471
- if (!activeFrame) activeFrame = requestAnimationFrame(updateActiveIndex);
910
+ /** 跟随滚动容器、正文和指示器自身的尺寸变化。 */
911
+ const observeHostSize = () => {
912
+ hostResizeObserver?.disconnect();
913
+ hostResizeObserver = void 0;
914
+ if (typeof ResizeObserver !== "undefined") {
915
+ hostResizeObserver = new ResizeObserver(updateLayout);
916
+ const host = usesParentScroller ? parentScroller?.wrapper : scrollHost instanceof HTMLElement ? scrollHost : void 0;
917
+ if (host) hostResizeObserver.observe(host);
918
+ if (props.target) hostResizeObserver.observe(props.target);
919
+ if (viewport.value) hostResizeObserver.observe(viewport.value);
920
+ if (indicatorRoot.value) hostResizeObserver.observe(indicatorRoot.value);
921
+ const parent = indicatorRoot.value?.parentElement;
922
+ if (parent) hostResizeObserver.observe(parent);
923
+ const layout = indicatorRoot.value?.closest(LAYOUT_SELECTOR);
924
+ if (layout instanceof HTMLElement) hostResizeObserver.observe(layout);
925
+ const startRail = layout?.querySelector(RAIL_START_SELECTOR);
926
+ const endRail = layout?.querySelector(RAIL_END_SELECTOR);
927
+ if (startRail instanceof HTMLElement) hostResizeObserver.observe(startRail);
928
+ if (endRail instanceof HTMLElement) hostResizeObserver.observe(endRail);
929
+ }
930
+ updateLayout();
472
931
  };
473
- /** 从最新渲染的 Markdown DOM 重建文档刻度。 */
932
+ /**
933
+ * 计算文档块相对正文顶部的比例,用于铺在固定高度轨道上。
934
+ * @param element 当前文档块。
935
+ * @param article 正文根节点。
936
+ * @returns 0–1 的纵向比例。
937
+ */
938
+ const getBlockRatio = (element, article) => {
939
+ const articleHeight = article.offsetHeight || article.getBoundingClientRect().height;
940
+ if (articleHeight <= 0) return 0;
941
+ const top = element.getBoundingClientRect().top - article.getBoundingClientRect().top;
942
+ return Math.min(1, Math.max(0, top / articleHeight));
943
+ };
944
+ /** 从最新渲染的 Markdown DOM 重建小地图刻度。 */
474
945
  const refreshMarkers = () => {
475
946
  refreshFrame = 0;
476
947
  const target = props.target;
@@ -478,67 +949,28 @@ var indicator_default = /* @__PURE__ */ defineComponent({
478
949
  markers.value = [];
479
950
  return;
480
951
  }
481
- const elements = [...target.querySelectorAll(BLOCK_SELECTOR)].filter((element) => !isNestedBlock(element));
482
- let sectionTitle = t("markdown.indicator.document");
483
- markers.value = elements.map((element, index) => {
484
- const isHeading = /^H[1-6]$/.test(element.tagName);
485
- if (isHeading) sectionTitle = getHeadingTitle(element);
486
- const nextElement = elements[index + 1];
487
- const content = isHeading && nextElement && !/^H[1-6]$/.test(nextElement.tagName) ? getBlockText(nextElement) : isHeading ? "" : getBlockText(element);
488
- return {
489
- ariaLabel: getAriaLabel(sectionTitle, content),
490
- content,
491
- element,
492
- id: `${index}-${element.id || element.tagName}`,
493
- title: sectionTitle
494
- };
495
- });
952
+ const headings = collectMarkdownHeadings(target);
953
+ markers.value = headings.map((heading, index) => ({
954
+ content: getPreviewContent(heading.element, headings[index + 1]?.element),
955
+ element: heading.element,
956
+ id: heading.id || `${index}`,
957
+ isHeading: true,
958
+ ratio: getBlockRatio(heading.element, target),
959
+ title: heading.text
960
+ }));
496
961
  hoverIndex.value = void 0;
497
- updateActiveIndex();
498
- followActiveMarker(activeIndex.value);
962
+ updateWindow();
499
963
  };
500
964
  /** 合并 MutationObserver 的密集通知,避免 Playground 挂载时重复扫描。 */
501
965
  const scheduleRefresh = () => {
502
966
  if (!refreshFrame) refreshFrame = requestAnimationFrame(refreshMarkers);
503
967
  };
504
- /**
505
- * 读取当前滚动容器的可视高度。
506
- * sticky 的百分比 top 会相对整篇文档计算,不能用来做容器垂直居中。
507
- * @returns 滚动容器 clientHeight,窗口滚动时回退到 innerHeight。
508
- */
509
- const getScrollHostClientHeight = () => {
510
- if (usesParentScroller) return parentScroller?.wrapper?.clientHeight || parentScroller?.clientHeight || window.innerHeight;
511
- if (scrollHost instanceof HTMLElement) return scrollHost.clientHeight;
512
- return window.innerHeight;
513
- };
514
- /**
515
- * 读取指示器可视高度:数字配置优先,否则测量已挂载节点或回退默认值。
516
- * @returns 指示器高度,单位 px。
517
- */
518
- const resolveIndicatorHeight = () => {
519
- const height = props.options.height;
520
- if (typeof height === "number" && Number.isFinite(height) && height > 0) return height;
521
- const measured = viewport.value?.getBoundingClientRect().height || 0;
522
- if (measured > 0) return measured;
523
- return Math.min(window.innerHeight * .72, 600);
524
- };
525
- /** 把指示器钉在滚动容器可视区域的垂直中心。 */
526
- const updateStickyInset = () => {
527
- const hostHeight = getScrollHostClientHeight();
528
- const indicatorHeight = resolveIndicatorHeight();
529
- stickyInset.value = hostHeight > 0 ? `${Math.max(0, (hostHeight - indicatorHeight) / 2)}px` : "0px";
530
- };
531
- /** 跟随滚动容器和指示器自身的尺寸变化,保持垂直居中。 */
532
- const observeHostSize = () => {
533
- hostResizeObserver?.disconnect();
534
- hostResizeObserver = void 0;
535
- if (typeof ResizeObserver !== "undefined") {
536
- hostResizeObserver = new ResizeObserver(updateStickyInset);
537
- const host = usesParentScroller ? parentScroller?.wrapper : scrollHost instanceof HTMLElement ? scrollHost : void 0;
538
- if (host) hostResizeObserver.observe(host);
539
- if (viewport.value) hostResizeObserver.observe(viewport.value);
540
- }
541
- updateStickyInset();
968
+ /** 将连续滚动事件合并到下一帧:更新可视窗口,并按父级底部收缩高度。 */
969
+ const handleScroll = () => {
970
+ if (!windowFrame) windowFrame = requestAnimationFrame(() => {
971
+ updateCappedHeight();
972
+ updateWindow();
973
+ });
542
974
  };
543
975
  /** 解除旧文档的观察和滚动监听。 */
544
976
  const cleanupTarget = () => {
@@ -546,7 +978,7 @@ var indicator_default = /* @__PURE__ */ defineComponent({
546
978
  observer = void 0;
547
979
  hostResizeObserver?.disconnect();
548
980
  hostResizeObserver = void 0;
549
- window.removeEventListener("resize", updateStickyInset);
981
+ window.removeEventListener("resize", updateLayout);
550
982
  if (usesParentScroller) parentScroller?.off?.(handleScroll);
551
983
  else if (scrollHost) scrollHost.removeEventListener("scroll", handleScroll);
552
984
  usesParentScroller = false;
@@ -570,7 +1002,7 @@ var indicator_default = /* @__PURE__ */ defineComponent({
570
1002
  scrollHost = getScrollHost();
571
1003
  scrollHost.addEventListener("scroll", handleScroll, { passive: true });
572
1004
  }
573
- window.addEventListener("resize", updateStickyInset, { passive: true });
1005
+ window.addEventListener("resize", updateLayout, { passive: true });
574
1006
  observer = new MutationObserver(scheduleRefresh);
575
1007
  observer.observe(props.target, {
576
1008
  childList: true,
@@ -581,18 +1013,16 @@ var indicator_default = /* @__PURE__ */ defineComponent({
581
1013
  observeHostSize();
582
1014
  };
583
1015
  /**
584
- * 将指示器内的纵坐标转换为最近的文档块序号。
585
- * @param event 当前指针事件。
586
- * @returns 距离指针最近的文档块序号。
1016
+ * 把轨道上的指针位置映射到最近的刻度,只用于悬停预览。
1017
+ * @param ratio 轨道 0–1 比例。
1018
+ * @returns 最近刻度序号。
587
1019
  */
588
- const getIndexByPointer = (event) => {
589
- if (!rail.value || !markers.value.length) return 0;
590
- const elements = [...rail.value.querySelectorAll(".docs-markdown-indicator__marker")];
1020
+ const getIndexByRatio = (ratio) => {
1021
+ if (!markers.value.length) return 0;
591
1022
  let closestIndex = 0;
592
1023
  let closestDistance = Number.POSITIVE_INFINITY;
593
- elements.forEach((element, index) => {
594
- const bounds = element.getBoundingClientRect();
595
- const distance = Math.abs(event.clientY - (bounds.top + bounds.height / 2));
1024
+ markers.value.forEach((marker, index) => {
1025
+ const distance = Math.abs(marker.ratio - ratio);
596
1026
  if (distance < closestDistance) {
597
1027
  closestIndex = index;
598
1028
  closestDistance = distance;
@@ -601,66 +1031,73 @@ var indicator_default = /* @__PURE__ */ defineComponent({
601
1031
  return closestIndex;
602
1032
  };
603
1033
  /**
604
- * 让摘要跟随当前刻度,同时限制在指示器可视高度内。
605
- * @param index 当前刻度序号。
1034
+ * 读取指针相对轨道的比例。
1035
+ * @param event 当前指针事件。
1036
+ * @returns 0–1 比例。
1037
+ */
1038
+ const getPointerRatio = (event) => {
1039
+ const bounds = rail.value?.getBoundingClientRect();
1040
+ if (!bounds) return 0;
1041
+ return getMinimapPointerRatio(event.clientY, bounds.top, bounds.height);
1042
+ };
1043
+ /**
1044
+ * 让摘要跟随指针,同时限制在指示器可视高度内。
606
1045
  * @param clientY 指针的视口纵坐标。
607
1046
  */
608
- const updatePreviewPosition = (index, clientY) => {
1047
+ const updatePreviewPosition = (clientY) => {
609
1048
  const rootBounds = indicatorRoot.value?.getBoundingClientRect();
610
1049
  const viewportBounds = viewport.value?.getBoundingClientRect();
611
- const marker = rail.value?.querySelectorAll(".docs-markdown-indicator__marker")[index];
612
- if (!rootBounds || !viewportBounds || !marker) return;
613
- const markerBounds = marker.getBoundingClientRect();
614
- const relativeY = (clientY ?? markerBounds.top + markerBounds.height / 2) - rootBounds.top;
615
- previewTop.value = Math.min(viewportBounds.height - 48, Math.max(48, relativeY));
1050
+ if (!rootBounds || !viewportBounds) return;
1051
+ previewTop.value = Math.min(viewportBounds.height - 48, Math.max(48, clientY - rootBounds.top));
616
1052
  };
617
1053
  /**
618
- * 滚动到指定文档块;拖动使用即时定位,点击使用平滑定位。
619
- * @param index 目标文档块序号。
1054
+ * 按轨道比例滚动正文,不改 hash。
1055
+ * @param ratio 轨道 0–1 比例。
620
1056
  * @param behavior 滚动行为。
621
1057
  */
622
- const scrollToMarker = (index, behavior) => {
623
- const marker = markers.value[index];
624
- if (!marker || !scrollHost) return;
625
- const markerTop = marker.element.getBoundingClientRect().top;
626
- if (usesParentScroller && parentScroller?.setScrollTop) parentScroller.setScrollTop((parentScroller.scrollTop || 0) + markerTop - getScrollHostTop() - 24);
627
- else if (scrollHost instanceof HTMLElement) {
628
- const hostTop = scrollHost.getBoundingClientRect().top;
1058
+ const scrollToRatio = (ratio, behavior) => {
1059
+ const metrics = getScrollMetrics();
1060
+ const nextTop = ratio * Math.max(0, metrics.scrollHeight - metrics.clientHeight);
1061
+ if (usesParentScroller && parentScroller?.setScrollTop) {
1062
+ parentScroller.setScrollTop(nextTop);
1063
+ return;
1064
+ }
1065
+ if (scrollHost instanceof HTMLElement) {
629
1066
  scrollHost.scrollTo({
630
- top: scrollHost.scrollTop + markerTop - hostTop - 24,
631
- behavior
1067
+ behavior,
1068
+ top: nextTop
632
1069
  });
633
- } else window.scrollTo({
634
- top: window.scrollY + markerTop - 24,
635
- behavior
1070
+ return;
1071
+ }
1072
+ window.scrollTo({
1073
+ behavior,
1074
+ top: nextTop
636
1075
  });
637
- activeIndex.value = index;
638
- followActiveMarker(index);
639
1076
  };
640
1077
  /**
641
- * 指针移动时展示摘要;按下拖动时同步快速浏览文档。
1078
+ * 指针移动时展示摘要;按下拖动时按比例 scrub 正文。
642
1079
  * @param event 当前指针事件。
643
1080
  */
644
1081
  const handlePointerMove = (event) => {
645
- const index = getIndexByPointer(event);
646
- hoverIndex.value = index;
647
- updatePreviewPosition(index, event.clientY);
648
- if (dragging.value) scrollToMarker(index, "auto");
1082
+ const ratio = getPointerRatio(event);
1083
+ hoverIndex.value = getIndexByRatio(ratio);
1084
+ updatePreviewPosition(event.clientY);
1085
+ if (dragging.value) scrollToRatio(ratio, "auto");
649
1086
  };
650
1087
  /**
651
- * 开始拖动指示器并定位到按下位置。
1088
+ * 开始 scrub:点击即按比例定位,可拖动时再捕获指针。
652
1089
  * @param event 当前指针事件。
653
1090
  */
654
1091
  const handlePointerDown = (event) => {
655
1092
  if (event.button !== 0) return;
1093
+ const ratio = getPointerRatio(event);
1094
+ hoverIndex.value = getIndexByRatio(ratio);
1095
+ updatePreviewPosition(event.clientY);
1096
+ scrollToRatio(ratio, "auto");
656
1097
  dragging.value = props.options.draggable !== false;
657
- const index = getIndexByPointer(event);
658
- hoverIndex.value = index;
659
- updatePreviewPosition(index, event.clientY);
660
1098
  if (dragging.value) {
661
1099
  captureTarget = event.currentTarget;
662
1100
  captureTarget.setPointerCapture?.(event.pointerId);
663
- scrollToMarker(index, "auto");
664
1101
  event.preventDefault();
665
1102
  }
666
1103
  };
@@ -678,96 +1115,99 @@ var indicator_default = /* @__PURE__ */ defineComponent({
678
1115
  if (!dragging.value) hoverIndex.value = void 0;
679
1116
  };
680
1117
  /**
681
- * 点击单条刻度时平滑定位到对应文档块。
682
- * @param index 目标文档块序号。
683
- */
684
- const handleClick = (index) => {
685
- hoverIndex.value = index;
686
- updatePreviewPosition(index);
687
- scrollToMarker(index, "smooth");
688
- };
689
- /**
690
- * 计算刻度位置、基础宽度和靠近指针时的鱼眼展开宽度。
1118
+ * 计算刻度在轨道上的位置、标题/正文宽度,以及靠近指针时的鱼眼展开。
691
1119
  * @param index 当前刻度序号。
692
1120
  * @returns 刻度的定位和宽度样式。
693
1121
  */
694
1122
  const getMarkerStyle = (index) => {
1123
+ const marker = markers.value[index];
695
1124
  const distance = typeof hoverIndex.value === "number" ? Math.abs(index - hoverIndex.value) : Number.POSITIVE_INFINITY;
696
- return { width: `${distance <= 3 ? [
697
- 28,
698
- 22,
699
- 16,
700
- 10
701
- ][distance] : 8}px` };
1125
+ const base = marker?.isHeading ? 10 : 6;
1126
+ const expandedWidths = [
1127
+ base + 6,
1128
+ base + 4,
1129
+ base + 2,
1130
+ base + 1
1131
+ ];
1132
+ const hoverWidth = distance <= 3 ? expandedWidths[distance] : base;
1133
+ const activeWidth = index === activeIndex.value ? base + 8 : base;
1134
+ return {
1135
+ top: `${(marker?.ratio || 0) * 100}%`,
1136
+ width: `${Math.max(hoverWidth, activeWidth)}px`
1137
+ };
702
1138
  };
703
- watch([() => props.target, localeName], setupTarget, { immediate: true });
704
- watch([() => props.options.height, viewport], () => {
1139
+ watch([() => props.target, localeName], setupTarget, {
1140
+ immediate: true,
1141
+ flush: "post"
1142
+ });
1143
+ watch(indicatorRoot, (root) => {
1144
+ if (root) observeHostSize();
1145
+ }, { flush: "post" });
1146
+ watch([
1147
+ () => props.options.height,
1148
+ () => props.options.position,
1149
+ viewport
1150
+ ], () => {
705
1151
  if (viewport.value && hostResizeObserver) hostResizeObserver.observe(viewport.value);
706
- updateStickyInset();
1152
+ updateLayout();
707
1153
  });
708
1154
  onBeforeUnmount(() => {
709
1155
  targetGeneration++;
710
1156
  cleanupTarget();
711
1157
  if (refreshFrame) cancelAnimationFrame(refreshFrame);
712
- if (activeFrame) cancelAnimationFrame(activeFrame);
1158
+ if (windowFrame) cancelAnimationFrame(windowFrame);
713
1159
  });
714
1160
  return (_ctx, _cache) => {
715
- return markers.value.length > 1 ? (openBlock(), createElementBlock("aside", {
1161
+ return markers.value.length ? (openBlock(), createElementBlock("aside", {
716
1162
  key: 0,
717
1163
  ref_key: "indicatorRoot",
718
1164
  ref: indicatorRoot,
719
- class: normalizeClass(["docs-markdown-indicator", `is-${__props.options.position || "right"}`]),
1165
+ class: normalizeClass(["docs-markdown-indicator", `is-${resolvedPosition.value}`]),
720
1166
  style: normalizeStyle(rootStyle.value),
721
1167
  "aria-label": unref(t)("markdown.indicator.label")
722
1168
  }, [createElementVNode("div", {
723
1169
  ref_key: "viewport",
724
1170
  ref: viewport,
725
- class: "docs-markdown-indicator__viewport",
1171
+ class: normalizeClass(["docs-markdown-indicator__viewport", { "is-dragging": dragging.value }]),
1172
+ role: "slider",
1173
+ "aria-orientation": "vertical",
1174
+ "aria-valuemin": 0,
1175
+ "aria-valuemax": 100,
1176
+ "aria-valuenow": scrollProgress.value,
1177
+ "aria-valuetext": `${scrollProgress.value}%`,
1178
+ style: normalizeStyle(viewportStyle.value),
726
1179
  onPointermove: handlePointerMove,
727
1180
  onPointerleave: handlePointerLeave,
728
1181
  onPointerdown: handlePointerDown,
729
1182
  onPointerup: handlePointerUp,
730
1183
  onPointercancel: handlePointerUp
731
- }, [createVNode(unref(Scroller), {
732
- ref_key: "indicatorScroller",
733
- ref: indicatorScroller,
734
- class: "docs-markdown-indicator__scroller",
735
- "auto-resize": true,
736
- native: false,
737
- "show-bar": true,
738
- height: "100%",
739
- "wrapper-style": "overflow-x: hidden;"
740
- }, {
741
- default: withCtx(() => [createElementVNode("div", {
742
- ref_key: "rail",
743
- ref: rail,
744
- class: "docs-markdown-indicator__rail"
745
- }, [createElementVNode("div", _hoisted_2, [(openBlock(true), createElementBlock(Fragment, null, renderList(markers.value, (marker, index) => {
746
- return openBlock(), createElementBlock("button", {
747
- key: marker.id,
748
- type: "button",
749
- class: normalizeClass(["docs-markdown-indicator__marker", {
750
- "is-active": index === activeIndex.value,
751
- "is-hovered": index === hoverIndex.value
752
- }]),
753
- style: normalizeStyle(getMarkerStyle(index)),
754
- "aria-label": marker.ariaLabel,
755
- onClick: ($event) => handleClick(index)
756
- }, null, 14, _hoisted_3);
757
- }), 128))])], 512)]),
758
- _: 1
759
- }, 512)], 544), __props.options.preview !== false && hoveredMarker.value ? (openBlock(), createElementBlock("div", {
1184
+ }, [createElementVNode("div", {
1185
+ ref_key: "rail",
1186
+ ref: rail,
1187
+ class: "docs-markdown-indicator__rail"
1188
+ }, [createElementVNode("div", {
1189
+ class: "docs-markdown-indicator__window",
1190
+ style: normalizeStyle(windowStyle.value)
1191
+ }, null, 4), (openBlock(true), createElementBlock(Fragment, null, renderList(markers.value, (marker, index) => {
1192
+ return openBlock(), createElementBlock("div", {
1193
+ key: marker.id,
1194
+ class: normalizeClass(["docs-markdown-indicator__marker", {
1195
+ "is-heading": marker.isHeading,
1196
+ "is-hovered": index === hoverIndex.value,
1197
+ "is-active": index === activeIndex.value,
1198
+ "is-in-view": isMarkerInView(index)
1199
+ }]),
1200
+ style: normalizeStyle(getMarkerStyle(index))
1201
+ }, null, 6);
1202
+ }), 128))], 512)], 46, _hoisted_2), __props.options.preview !== false && !dragging.value && hoveredMarker.value ? (openBlock(), createElementBlock("div", {
760
1203
  key: 0,
761
1204
  class: "docs-markdown-indicator__preview",
762
1205
  style: normalizeStyle(previewStyle.value)
763
- }, [createElementVNode("div", _hoisted_4, toDisplayString(hoveredMarker.value.title), 1), hoveredMarker.value.content ? (openBlock(), createElementBlock("div", _hoisted_5, toDisplayString(hoveredMarker.value.content), 1)) : createCommentVNode("v-if", true)], 4)) : createCommentVNode("v-if", true)], 14, _hoisted_1$1)) : createCommentVNode("v-if", true);
1206
+ }, [createElementVNode("div", _hoisted_3, toDisplayString(hoveredMarker.value.title), 1), hoveredMarker.value.content ? (openBlock(), createElementBlock("div", _hoisted_4, toDisplayString(hoveredMarker.value.content), 1)) : createCommentVNode("v-if", true)], 4)) : createCommentVNode("v-if", true)], 14, _hoisted_1)) : createCommentVNode("v-if", true);
764
1207
  };
765
1208
  }
766
1209
  });
767
1210
  //#endregion
768
- //#region packages/markdown/src/wrapper.vue?vue&type=script&setup=true&lang.ts
769
- var _hoisted_1 = { class: "docs-markdown" };
770
- //#endregion
771
1211
  //#region packages/markdown/src/wrapper.vue
772
1212
  var wrapper_default = /* @__PURE__ */ defineComponent({
773
1213
  __name: "wrapper",
@@ -777,6 +1217,9 @@ var wrapper_default = /* @__PURE__ */ defineComponent({
777
1217
  default: true
778
1218
  },
779
1219
  locale: {},
1220
+ theme: { default: "default" },
1221
+ playground: { default: () => ({}) },
1222
+ tabQuery: {},
780
1223
  modelValue: {},
781
1224
  value: {}
782
1225
  },
@@ -791,13 +1234,19 @@ var wrapper_default = /* @__PURE__ */ defineComponent({
791
1234
  if (props.indicator === false) return void 0;
792
1235
  return typeof props.indicator === "object" ? props.indicator : {};
793
1236
  });
1237
+ const resolvedTheme = computed(() => props.theme === "traditional" ? "traditional" : "default");
1238
+ const injectedTabQuery = inject(markdownTabQueryKey, null);
1239
+ const historyTabQuery = createHistoryTabQueryAdapter();
1240
+ const resolvedTabQuery = computed(() => props.tabQuery || injectedTabQuery || historyTabQuery);
794
1241
  const source = computed(() => typeof props.modelValue === "string" ? props.modelValue : props.value);
795
1242
  const markdownBinding = computed(() => ({
796
1243
  source: source.value,
797
- locale: locale.value
1244
+ locale: locale.value,
1245
+ playground: props.playground,
1246
+ tabQuery: resolvedTabQuery.value
798
1247
  }));
799
1248
  return (_ctx, _cache) => {
800
- return openBlock(), createElementBlock("div", _hoisted_1, [indicatorOptions.value ? (openBlock(), createBlock(indicator_default, {
1249
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["docs-markdown", `docs-markdown--${resolvedTheme.value}`]) }, [indicatorOptions.value && content.value ? (openBlock(), createBlock(indicator_default, {
801
1250
  key: 0,
802
1251
  target: content.value,
803
1252
  options: indicatorOptions.value
@@ -805,9 +1254,9 @@ var wrapper_default = /* @__PURE__ */ defineComponent({
805
1254
  ref_key: "content",
806
1255
  ref: content,
807
1256
  class: "docs-markdown-reset"
808
- }, null, 512), [[unref(vMarkdown), markdownBinding.value]])]);
1257
+ }, null, 512), [[unref(vMarkdown), markdownBinding.value]])], 2);
809
1258
  };
810
1259
  }
811
1260
  });
812
1261
  //#endregion
813
- export { wrapper_default as Markdown, parseMarkdownSearchSections };
1262
+ export { wrapper_default as Markdown, collectMarkdownHeadings, createHistoryTabQueryAdapter, getMarkdownHeadingTitle, markdownTabQueryKey, parseMarkdownSearchSections, toMarkdownTabId };