@deot/docs-markdown 1.0.10 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -34,12 +34,39 @@ markdown_it = __toESM(markdown_it);
34
34
  let json5 = require("json5");
35
35
  json5 = __toESM(json5);
36
36
  let _deot_helper_dom = require("@deot/helper-dom");
37
- let _deot_vc = require("@deot/vc");
38
37
  //#region packages/markdown/src/markdown.ts
39
38
  var HTML_MD_SIGN = "md";
40
39
  var PLAYGROUND = "playground";
41
40
  var TIP = "tip";
42
41
  var WARNING = "warning";
42
+ var CALLOUT_FILLED_RE = /(^|\s)filled(\s|$)/;
43
+ var CALLOUT_INFO_ICON = [
44
+ "<svg width=\"22\" height=\"28\" viewBox=\"0 0 22 28\" fill=\"none\" aria-hidden=\"true\">",
45
+ "<circle class=\"docs-markdown-callout-icon__fill\" cx=\"11\" cy=\"14\" r=\"11\"></circle>",
46
+ "<circle class=\"docs-markdown-callout-icon__ring\" cx=\"11\" cy=\"14\" r=\"10.5\"></circle>",
47
+ "<path class=\"docs-markdown-callout-icon__mark\"",
48
+ " 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\"",
49
+ " stroke-width=\"1.5\" stroke-linecap=\"round\"></path>",
50
+ "<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>",
51
+ "</svg>"
52
+ ].join("");
53
+ var CALLOUT_WARNING_ICON = [
54
+ "<svg width=\"22\" height=\"28\" viewBox=\"0 0 22 28\" fill=\"none\" aria-hidden=\"true\">",
55
+ "<circle class=\"docs-markdown-callout-icon__fill\" cx=\"11\" cy=\"14\" r=\"11\"></circle>",
56
+ "<circle class=\"docs-markdown-callout-icon__ring\" cx=\"11\" cy=\"14\" r=\"10.5\"></circle>",
57
+ "<path class=\"docs-markdown-callout-icon__mark\" d=\"M11 10v5.5\" stroke-width=\"1.5\" stroke-linecap=\"round\"></path>",
58
+ "<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>",
59
+ "</svg>"
60
+ ].join("");
61
+ var renderCallout = (name, icon) => (tokens, idx) => {
62
+ if (tokens[idx].nesting === 1) return [
63
+ `<div class="${CALLOUT_FILLED_RE.test(tokens[idx].info) ? `${name} ${name}--filled` : name}" md>`,
64
+ "<span class=\"docs-markdown-callout-icon\" aria-hidden=\"true\">",
65
+ icon,
66
+ "</span><div class=\"docs-markdown-callout-body\">\n"
67
+ ].join("");
68
+ return "</div></div>\n";
69
+ };
43
70
  var config = new markdown_it_chain.default();
44
71
  config.options.html(true).typographer(true).linkify(true).end().plugin("anchor").use(markdown_it_anchor.default, [{ permalink: markdown_it_anchor.default.permalink.linkInsideHeader({
45
72
  placement: "before",
@@ -49,8 +76,8 @@ config.options.html(true).typographer(true).linkify(true).end().plugin("anchor")
49
76
  $md.use(markdown_it_container.default, PLAYGROUND, { validate(params) {
50
77
  return params.trim().match(reg);
51
78
  } });
52
- $md.use(markdown_it_container.default, TIP);
53
- $md.use(markdown_it_container.default, WARNING);
79
+ $md.use(markdown_it_container.default, TIP, { render: renderCallout(TIP, CALLOUT_INFO_ICON) });
80
+ $md.use(markdown_it_container.default, WARNING, { render: renderCallout(WARNING, CALLOUT_WARNING_ICON) });
54
81
  }).end();
55
82
  var md = config.toMd(markdown_it.default);
56
83
  var playgroundOpen = `container_${PLAYGROUND}_open`;
@@ -97,12 +124,22 @@ var validatePreviewInset = (propsData) => {
97
124
  if (Array.isArray(inset) && inset.length === 2 && inset.every(isPreviewInsetValue)) return "";
98
125
  return "previewInset 必须是非负数或 [垂直,水平] 非负数数组";
99
126
  };
100
- var validateExpand = (propsData) => {
101
- if (!("expand" in propsData)) return "";
102
- const expand = propsData.expand;
103
- if (expand === true) return "";
104
- if (typeof expand === "number" && Number.isFinite(expand) && expand > 0) return "";
105
- return "expand 必须是 true 或正数";
127
+ var validateExpandable = (propsData) => {
128
+ if (!("expandable" in propsData)) return "";
129
+ const expandable = propsData.expandable;
130
+ if (expandable === true) return "";
131
+ if (typeof expandable === "number" && Number.isFinite(expandable) && expandable > 0) return "";
132
+ return "expandable 必须是 true 或正数";
133
+ };
134
+ var validateTitle = (propsData) => {
135
+ if (!("title" in propsData)) return "";
136
+ if (typeof propsData.title === "string") return "";
137
+ return "title 必须是字符串";
138
+ };
139
+ var validateTitleId = (propsData) => {
140
+ if (!("id" in propsData)) return "";
141
+ if (typeof propsData.id === "string") return "";
142
+ return "id 必须是字符串";
106
143
  };
107
144
  var parseRuntimeProps = (tokens) => {
108
145
  for (const token of tokens) {
@@ -140,7 +177,7 @@ md.core.ruler.after("block", "runtime-files", (state) => {
140
177
  const placeholder = new state.Token("html_block", "", 0);
141
178
  placeholder.block = true;
142
179
  const propsData = parseRuntimeProps(innerTokens);
143
- const propsError = validateRuntimeViews(propsData) || validateRuntimeViewport(propsData) || validatePreviewInset(propsData) || validateExpand(propsData);
180
+ const propsError = validateRuntimeViews(propsData) || validateRuntimeViewport(propsData) || validatePreviewInset(propsData) || validateExpandable(propsData) || validateTitle(propsData) || validateTitleId(propsData);
144
181
  const propsAttr = renderPlaygroundAttrs(propsData);
145
182
  if (!fences.length) placeholder.content = renderPlaygroundError("至少需要声明一个代码文件");
146
183
  else if (propsError) placeholder.content = renderPlaygroundError(propsError);
@@ -251,6 +288,7 @@ var cleanup = (el) => {
251
288
  var resolveBlockLanguage = (block) => {
252
289
  return [...block.classList].find((className) => className.startsWith("language-"))?.slice(9) || "";
253
290
  };
291
+ var serializePlaygroundDefaults = (value) => JSON.stringify(value || {});
254
292
  var render = async (el, binding) => {
255
293
  cleanup(el);
256
294
  const version = (renderVersions.get(el) || 0) + 1;
@@ -279,6 +317,7 @@ var render = async (el, binding) => {
279
317
  app.mount(mountPoint);
280
318
  apps.push(app);
281
319
  });
320
+ const sitePlaygroundDefaults = binding.value.playground && typeof binding.value.playground === "object" ? binding.value.playground : {};
282
321
  playgrounds.forEach((item) => {
283
322
  const code = item.dataset.code;
284
323
  let files;
@@ -294,6 +333,7 @@ var render = async (el, binding) => {
294
333
  entry: item.dataset.entry
295
334
  } : { modelValue: code };
296
335
  const app = (0, vue.createApp)(() => (0, vue.h)(DocsPlayground.Playground, {
336
+ ...sitePlaygroundDefaults,
297
337
  ...typeof propsData === "object" ? propsData : {},
298
338
  ...runtimeProps,
299
339
  locale: binding.value.locale
@@ -304,7 +344,7 @@ var render = async (el, binding) => {
304
344
  mountedApps.set(el, apps);
305
345
  };
306
346
  var update = (el, binding) => {
307
- if (binding.value.source !== binding.oldValue?.source || binding.value.locale !== binding.oldValue?.locale) render(el, binding);
347
+ if (binding.value.source !== binding.oldValue?.source || binding.value.locale !== binding.oldValue?.locale || serializePlaygroundDefaults(binding.value.playground) !== serializePlaygroundDefaults(binding.oldValue?.playground)) render(el, binding);
308
348
  };
309
349
  var vMarkdown = {
310
350
  mounted: (el, binding) => void render(el, binding),
@@ -314,17 +354,148 @@ var vMarkdown = {
314
354
  cleanup(el);
315
355
  }
316
356
  };
357
+ /**
358
+ * 指示器高度不能超过正文实际高度,也不能超出当前滚动容器可视区域。
359
+ * 与文档壳大纲的封顶规则一致。
360
+ * @param articleHeight 正文高度。
361
+ * @param viewportHeight 主滚动容器可视高度。
362
+ * @returns 指示器最大高度,单位 px。
363
+ */
364
+ var capIndicatorMaxHeight = (articleHeight, viewportHeight) => {
365
+ const article = Math.max(0, articleHeight);
366
+ const viewport = Math.max(0, viewportHeight);
367
+ if (!article) return viewport;
368
+ if (!viewport) return article;
369
+ return Math.min(article, viewport);
370
+ };
371
+ /**
372
+ * 滑轨高度再受 sticky 父级剩余空间约束,避免滚到文末盖住 footer。
373
+ * @param cap 正文/视口封顶后的高度。
374
+ * @param available 指示器顶部到父级底部的剩余像素;非有限值表示不额外约束。
375
+ * @returns 最终高度,单位 px。
376
+ */
377
+ var capStickyAvailableHeight = (cap, available) => {
378
+ const limited = Math.max(0, cap);
379
+ if (!Number.isFinite(available)) return limited;
380
+ return Math.min(limited, Math.max(0, available));
381
+ };
382
+ /**
383
+ * 把主滚动位置映射成小地图可视窗口(相对轨道的 0–1 比例)。
384
+ * @param scrollTop 当前滚动偏移。
385
+ * @param clientHeight 滚动容器可视高度。
386
+ * @param scrollHeight 滚动内容总高度。
387
+ * @returns 窗口顶部与高度比例。
388
+ */
389
+ var getMinimapWindow = (scrollTop, clientHeight, scrollHeight) => {
390
+ const total = Math.max(0, scrollHeight);
391
+ if (!total) return {
392
+ height: 1,
393
+ top: 0
394
+ };
395
+ const height = Math.min(1, Math.max(0, clientHeight / total));
396
+ const maxTop = Math.max(0, 1 - height);
397
+ return {
398
+ height,
399
+ top: maxTop <= 0 ? 0 : Math.min(maxTop, Math.max(0, scrollTop / total))
400
+ };
401
+ };
402
+ /**
403
+ * 按小地图窗口顶部选出当前阅读刻度:最后一个仍不晚于窗口顶的块。
404
+ * 滚到末屏时固定最后一条,避免末块永远到不了窗口顶。
405
+ * @param ratios 刻度在正文中的 0–1 比例,按出现顺序。
406
+ * @param windowTop 可视窗口顶部比例。
407
+ * @param windowHeight 可视窗口高度比例。
408
+ * @returns 当前刻度序号;没有刻度时为 -1。
409
+ */
410
+ var findActiveMinimapIndex = (ratios, windowTop, windowHeight) => {
411
+ if (!ratios.length) return -1;
412
+ if (windowTop + windowHeight >= .999) return ratios.length - 1;
413
+ let active = 0;
414
+ for (let index = 0; index < ratios.length; index++) if ((ratios[index] || 0) <= windowTop) active = index;
415
+ return active;
416
+ };
417
+ /**
418
+ * 判断刻度是否落在当前可视窗口内。
419
+ * @param ratio 刻度比例。
420
+ * @param windowTop 可视窗口顶部比例。
421
+ * @param windowHeight 可视窗口高度比例。
422
+ * @returns 是否位于窗口内。
423
+ */
424
+ var isMinimapMarkerInView = (ratio, windowTop, windowHeight) => {
425
+ const top = Math.max(0, windowTop);
426
+ const bottom = top + Math.max(0, windowHeight);
427
+ return ratio >= top && ratio <= bottom;
428
+ };
429
+ /**
430
+ * 把指针纵坐标映射成轨道上的 0–1 比例。
431
+ * @param clientY 指针视口纵坐标。
432
+ * @param trackTop 轨道顶部。
433
+ * @param trackHeight 轨道高度。
434
+ * @returns 夹紧后的比例。
435
+ */
436
+ var getMinimapPointerRatio = (clientY, trackTop, trackHeight) => {
437
+ if (trackHeight <= 0) return 0;
438
+ return Math.min(1, Math.max(0, (clientY - trackTop) / trackHeight));
439
+ };
440
+ /**
441
+ * 把指示器刻度对齐到文档壳斜纹轨的内侧,并留出 `INDICATOR_RAIL_GAP`。
442
+ * 左侧靠近 `rail--start` 的右缘,右侧靠近 `rail--end` 的左缘。
443
+ * 没有可见轨道时返回 undefined,由样式回退到正文边缘。
444
+ * @param root 指示器根节点相对视口的盒子。
445
+ * @param rail 当前侧斜纹轨的盒子。
446
+ * @param side 指示器所在侧。
447
+ * @param viewportWidth 刻度轨道宽度。
448
+ * @returns 相对指示器根节点的 left 偏移,单位 px。
449
+ */
450
+ var getIndicatorInlineOffset = (root, rail, side, viewportWidth) => {
451
+ if (!rail || rail.width <= 0 || rail.height <= 0) return;
452
+ if (side === "left") return rail.right - root.left + 8;
453
+ return rail.left - root.left - viewportWidth - 8;
454
+ };
455
+ //#endregion
456
+ //#region packages/markdown/src/headings.ts
457
+ var HEADING_SELECTOR = "h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]";
458
+ var SKIP_CLOSEST = "pre, .docs-markdown-code-preview, [data-playground][data-code], [data-playground][data-files]";
459
+ /**
460
+ * 去掉 markdown-it-anchor 的井号后,读取标题的可见文案。
461
+ * @param element 当前标题节点。
462
+ * @returns 单行章节标题。
463
+ */
464
+ var getMarkdownHeadingTitle = (element) => {
465
+ const clone = element.cloneNode(true);
466
+ clone.querySelectorAll(".header-anchor").forEach((node) => node.remove());
467
+ return (clone.textContent || "").replace(/\s+/g, " ").trim();
468
+ };
469
+ /**
470
+ * 从已渲染的 Markdown 中收集带 id 的标题,规则与文档壳大纲一致:
471
+ * 忽略代码块 / Playground 内部标题;有章节标题时不把页面 h1 算进去。
472
+ * @param root Markdown 所在容器。
473
+ * @returns 大纲条目,按出现顺序排列。
474
+ */
475
+ var collectMarkdownHeadings = (root) => {
476
+ if (!root || !("querySelectorAll" in root)) return [];
477
+ return [...root.querySelectorAll(HEADING_SELECTOR)].filter((element) => !element.closest(SKIP_CLOSEST) && Boolean(getMarkdownHeadingTitle(element))).map((element) => ({
478
+ element,
479
+ id: element.id,
480
+ level: Number(element.tagName.slice(1)),
481
+ text: getMarkdownHeadingTitle(element)
482
+ })).filter((item, _, headings) => item.level > 1 || headings.every((heading) => heading.level === 1));
483
+ };
317
484
  //#endregion
318
485
  //#region packages/markdown/src/indicator.vue?vue&type=script&setup=true&lang.ts
319
- var _hoisted_1$1 = ["aria-label"];
320
- var _hoisted_2 = { class: "docs-markdown-indicator__list" };
321
- var _hoisted_3 = ["aria-label", "onClick"];
322
- var _hoisted_4 = { class: "docs-markdown-indicator__preview-title" };
323
- var _hoisted_5 = {
486
+ var _hoisted_1 = ["aria-label"];
487
+ var _hoisted_2 = ["aria-valuenow", "aria-valuetext"];
488
+ var _hoisted_3 = { class: "docs-markdown-indicator__preview-title" };
489
+ var _hoisted_4 = {
324
490
  key: 0,
325
491
  class: "docs-markdown-indicator__preview-content"
326
492
  };
327
- var MAX_ARIA_LABEL_LENGTH = 180;
493
+ var LAYOUT_SELECTOR = ".docs-layout";
494
+ var RAIL_START_SELECTOR = ".docs-layout__rail--start";
495
+ var RAIL_END_SELECTOR = ".docs-layout__rail--end";
496
+ var VIEWPORT_WIDTH = 40;
497
+ /** 与 docs-page-outline 上下 padding 一致,需从轨道高度中扣除。 */
498
+ var INDICATOR_CHROME = 136;
328
499
  //#endregion
329
500
  //#region packages/markdown/src/indicator.vue
330
501
  var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
@@ -334,61 +505,82 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
334
505
  target: {}
335
506
  },
336
507
  setup(__props) {
337
- const BLOCK_SELECTOR = [
338
- "h1",
339
- "h2",
340
- "h3",
341
- "h4",
342
- "h5",
343
- "h6",
344
- "p",
345
- "li",
346
- "pre",
347
- "blockquote",
348
- "table",
349
- ".tip",
350
- ".warning",
351
- ".docs-markdown-code-preview",
352
- "[data-playground]"
353
- ].join(",");
354
- const CONTAINER_SELECTOR = [
355
- "li",
356
- "blockquote",
357
- "table",
358
- ".tip",
359
- ".warning",
360
- ".docs-markdown-code-preview",
361
- "[data-playground]"
362
- ].join(",");
363
508
  const props = __props;
364
509
  const { lang: localeName, t } = (0, _deot_docs_locale.useLocale)();
365
510
  const parentScroller = (0, vue.inject)("vc-scroller", null);
366
511
  const indicatorRoot = (0, vue.ref)();
367
512
  const viewport = (0, vue.ref)();
368
513
  const rail = (0, vue.ref)();
369
- const indicatorScroller = (0, vue.ref)();
370
514
  const markers = (0, vue.shallowRef)([]);
371
- const activeIndex = (0, vue.ref)(0);
372
515
  const hoverIndex = (0, vue.ref)();
516
+ const activeIndex = (0, vue.ref)(-1);
373
517
  const previewTop = (0, vue.ref)(0);
374
518
  const dragging = (0, vue.ref)(false);
375
- const stickyInset = (0, vue.ref)("0px");
519
+ const cappedHeight = (0, vue.ref)(0);
520
+ const inlineAnchor = (0, vue.ref)();
521
+ const windowBox = (0, vue.ref)(getMinimapWindow(0, 0, 0));
376
522
  let observer;
377
523
  let hostResizeObserver;
378
524
  let scrollHost;
379
525
  let usesParentScroller = false;
380
526
  let captureTarget;
381
527
  let refreshFrame = 0;
382
- let activeFrame = 0;
528
+ let windowFrame = 0;
383
529
  let targetGeneration = 0;
384
530
  const toCssLength = (value, fallback) => typeof value === "number" ? `${value}px` : value || fallback;
531
+ const resolvedHeight = (0, vue.computed)(() => {
532
+ const cap = cappedHeight.value;
533
+ const configured = props.options.height;
534
+ if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) return `${cap > 0 ? Math.min(configured, cap) : configured}px`;
535
+ if (typeof configured === "string" && configured.trim()) return configured;
536
+ if (cap > 0) return `${cap}px`;
537
+ return "calc(100svh - 60px)";
538
+ });
539
+ /** 扣除上下 padding 后的轨道高度。小地图轨高等于该值,不再内部滚动。 */
540
+ const resolvedTrackHeight = (0, vue.computed)(() => {
541
+ const height = resolvedHeight.value;
542
+ if (height.endsWith("px")) {
543
+ const total = Number.parseFloat(height);
544
+ if (Number.isFinite(total)) return `${Math.max(0, total - INDICATOR_CHROME)}px`;
545
+ }
546
+ return `calc(${height} - ${INDICATOR_CHROME}px)`;
547
+ });
385
548
  const rootStyle = (0, vue.computed)(() => ({
386
- "--docs-markdown-indicator-height": toCssLength(props.options.height, "min(72vh, 600px)"),
387
- "--docs-markdown-indicator-inset": stickyInset.value,
549
+ "--docs-markdown-indicator-height": resolvedHeight.value,
550
+ "--docs-markdown-indicator-track-height": resolvedTrackHeight.value,
388
551
  "--docs-markdown-indicator-top": toCssLength(props.options.top, "0px")
389
552
  }));
390
553
  const hoveredMarker = (0, vue.computed)(() => typeof hoverIndex.value === "number" ? markers.value[hoverIndex.value] : void 0);
391
- const previewStyle = (0, vue.computed)(() => ({ top: `${previewTop.value}px` }));
554
+ const isMarkerInView = (index) => {
555
+ const marker = markers.value[index];
556
+ if (!marker) return false;
557
+ return isMinimapMarkerInView(marker.ratio, windowBox.value.top, windowBox.value.height);
558
+ };
559
+ const resolvedPosition = (0, vue.computed)(() => props.options.position === "right" ? "right" : "left");
560
+ const scrollProgress = (0, vue.computed)(() => Math.round(windowBox.value.top * 100));
561
+ const viewportStyle = (0, vue.computed)(() => {
562
+ if (typeof inlineAnchor.value !== "number") return;
563
+ return {
564
+ left: `${inlineAnchor.value}px`,
565
+ right: "auto"
566
+ };
567
+ });
568
+ const windowStyle = (0, vue.computed)(() => ({
569
+ height: `${windowBox.value.height * 100}%`,
570
+ top: `${windowBox.value.top * 100}%`
571
+ }));
572
+ const previewStyle = (0, vue.computed)(() => {
573
+ const style = { top: `${previewTop.value}px` };
574
+ if (typeof inlineAnchor.value !== "number") return style;
575
+ if (resolvedPosition.value === "left") {
576
+ style.left = `${inlineAnchor.value + 24}px`;
577
+ style.right = "auto";
578
+ } else {
579
+ style.left = "auto";
580
+ style.right = `calc(100% - ${inlineAnchor.value}px)`;
581
+ }
582
+ return style;
583
+ });
392
584
  /**
393
585
  * 保留文档块的有效换行,同时折叠每行内部的多余空白。
394
586
  * @param element 当前文档块。
@@ -396,30 +588,21 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
396
588
  */
397
589
  const getBlockText = (element) => (element.textContent || "").split(/\n+/).map((line) => line.replace(/\s+/g, " ").trim()).filter(Boolean).join("\n");
398
590
  /**
399
- * 清理标题锚点产生的井号,并提供无标题内容的兜底文本。
400
- * @param element 当前标题节点。
401
- * @returns 单行章节标题。
402
- */
403
- const getHeadingTitle = (element) => getBlockText(element).replace(/^#\s*/, "").replace(/\s+/g, " ").trim() || t("markdown.indicator.untitled");
404
- /**
405
- * 为无障碍名称限制长度,避免长代码块生成过大的属性值。
406
- * @param title 当前章节标题。
407
- * @param content 当前文档块内容。
408
- * @returns 标题和内容组成的简短名称。
409
- */
410
- const getAriaLabel = (title, content) => {
411
- const value = content ? `${title}: ${content.replace(/\s+/g, " ")}` : title;
412
- if (value.length <= MAX_ARIA_LABEL_LENGTH) return value;
413
- return `${value.slice(0, MAX_ARIA_LABEL_LENGTH).trim()}…`;
414
- };
415
- /**
416
- * 判断候选节点是否已经包含在另一个可独立定位的文档块内。
417
- * @param element 当前候选节点。
418
- * @returns 是否应由外层文档块统一表示。
591
+ * 取当前标题到下一标题之间的第一段正文,作为悬停摘要。
592
+ * @param heading 当前标题。
593
+ * @param nextHeading 下一个标题,没有则为空。
594
+ * @returns 摘要文本。
419
595
  */
420
- const isNestedBlock = (element) => {
421
- const container = element.closest(CONTAINER_SELECTOR);
422
- return Boolean(container && container !== element);
596
+ const getPreviewContent = (heading, nextHeading) => {
597
+ let node = heading.nextElementSibling;
598
+ while (node && node !== nextHeading) {
599
+ if (!/^H[1-6]$/.test(node.tagName)) {
600
+ const text = getBlockText(node);
601
+ if (text) return text;
602
+ }
603
+ node = node.nextElementSibling;
604
+ }
605
+ return "";
423
606
  };
424
607
  /**
425
608
  * 获取 Markdown 使用的原生滚动宿主。
@@ -428,77 +611,118 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
428
611
  */
429
612
  const getScrollHost = () => (0, _deot_helper_dom.getScroller)(props.target) || window;
430
613
  /**
431
- * 获取当前滚动宿主的可视区域顶部。
432
- * @returns 相对于视口的顶部坐标。
614
+ * 读取当前滚动容器的可视高度。
615
+ * @returns 滚动容器 clientHeight,窗口滚动时回退到 innerHeight。
433
616
  */
434
- const getScrollHostTop = () => {
435
- if (usesParentScroller) return parentScroller?.wrapper?.getBoundingClientRect().top || 0;
436
- return scrollHost instanceof HTMLElement ? scrollHost.getBoundingClientRect().top : 0;
617
+ const getScrollHostClientHeight = () => {
618
+ if (usesParentScroller) return parentScroller?.wrapper?.clientHeight || parentScroller?.clientHeight || window.innerHeight;
619
+ if (scrollHost instanceof HTMLElement) return scrollHost.clientHeight;
620
+ return window.innerHeight;
437
621
  };
438
622
  /**
439
- * 判断文档是否已经到达滚动末尾。
440
- * 末屏通常无法把最后一个内容块推到顶部阈值,因此需要显式选中最后一条刻度。
441
- * @returns 是否位于可滚动内容末尾。
623
+ * 读取主滚动的位置与尺寸,供可视窗口和 scrub 共用。
624
+ * @returns 当前滚动偏移、可视高度和内容总高度。
442
625
  */
443
- const isScrollEnd = () => {
444
- let clientHeight;
445
- let scrollHeight;
446
- let scrollTop;
447
- if (usesParentScroller) {
448
- clientHeight = parentScroller?.clientHeight || 0;
449
- scrollHeight = parentScroller?.scrollHeight || 0;
450
- scrollTop = parentScroller?.scrollTop || 0;
451
- } else if (scrollHost instanceof HTMLElement) {
452
- clientHeight = scrollHost.clientHeight;
453
- scrollHeight = scrollHost.scrollHeight;
454
- scrollTop = scrollHost.scrollTop;
455
- } else {
456
- clientHeight = window.innerHeight;
457
- scrollHeight = document.documentElement.scrollHeight;
458
- scrollTop = window.scrollY;
459
- }
460
- return scrollHeight > clientHeight && scrollTop + clientHeight >= scrollHeight - 1;
626
+ const getScrollMetrics = () => {
627
+ if (usesParentScroller) return {
628
+ clientHeight: parentScroller?.clientHeight || parentScroller?.wrapper?.clientHeight || 0,
629
+ scrollHeight: parentScroller?.scrollHeight || parentScroller?.wrapper?.scrollHeight || 0,
630
+ scrollTop: parentScroller?.scrollTop || 0
631
+ };
632
+ if (scrollHost instanceof HTMLElement) return {
633
+ clientHeight: scrollHost.clientHeight,
634
+ scrollHeight: scrollHost.scrollHeight,
635
+ scrollTop: scrollHost.scrollTop
636
+ };
637
+ return {
638
+ clientHeight: window.innerHeight,
639
+ scrollHeight: document.documentElement.scrollHeight,
640
+ scrollTop: window.scrollY
641
+ };
461
642
  };
462
643
  /**
463
- * 保证当前阅读刻度处于内部 Scroller 的可视范围内。
464
- * @param index 当前阅读刻度序号。
644
+ * 按正文、主滚动可视高度,以及 sticky 父级剩余空间封顶,避免盖住 footer。
645
+ * @returns 指示器可用的最大高度,单位 px。
465
646
  */
466
- const followActiveMarker = async (index) => {
467
- await (0, vue.nextTick)();
468
- const marker = rail.value?.querySelectorAll(".docs-markdown-indicator__marker")[index];
469
- const wrapper = viewport.value?.querySelector(".vc-scroller__wrapper");
470
- if (!marker || !wrapper) return;
471
- const markerRect = marker.getBoundingClientRect();
472
- const wrapperRect = wrapper.getBoundingClientRect();
473
- const padding = 16;
474
- if (markerRect.top >= wrapperRect.top + padding && markerRect.bottom <= wrapperRect.bottom - padding) return;
475
- const markerCenter = markerRect.top + markerRect.height / 2;
476
- const wrapperCenter = wrapperRect.top + wrapperRect.height / 2;
477
- indicatorScroller.value?.setScrollTop(Math.max(0, wrapper.scrollTop + markerCenter - wrapperCenter));
647
+ const getStickyAvailableHeight = () => {
648
+ const root = indicatorRoot.value;
649
+ const parent = root?.parentElement;
650
+ if (!root || !parent) return Number.POSITIVE_INFINITY;
651
+ const parentBox = parent.getBoundingClientRect();
652
+ if (parentBox.height <= 0 && parentBox.bottom === 0) return Number.POSITIVE_INFINITY;
653
+ return parentBox.bottom - root.getBoundingClientRect().top;
478
654
  };
479
- /** 根据滚动位置更新当前阅读块,不触发 Markdown 内容重渲染。 */
480
- const updateActiveIndex = () => {
481
- activeFrame = 0;
482
- if (!markers.value.length) return;
483
- const threshold = getScrollHostTop() + 80;
484
- let nextIndex = markers.value.length - 1;
485
- if (!isScrollEnd()) {
486
- nextIndex = 0;
487
- for (let index = 0; index < markers.value.length; index++) {
488
- if (markers.value[index].element.getBoundingClientRect().top > threshold) break;
489
- nextIndex = index;
490
- }
655
+ const updateCappedHeight = () => {
656
+ cappedHeight.value = capStickyAvailableHeight(capIndicatorMaxHeight(props.target?.offsetHeight || 0, getScrollHostClientHeight()), getStickyAvailableHeight());
657
+ };
658
+ /** 根据主滚动位置更新小地图可视窗口和当前刻度。 */
659
+ const updateWindow = () => {
660
+ windowFrame = 0;
661
+ const metrics = getScrollMetrics();
662
+ windowBox.value = getMinimapWindow(metrics.scrollTop, metrics.clientHeight, metrics.scrollHeight);
663
+ activeIndex.value = findActiveMinimapIndex(markers.value.map((marker) => marker.ratio), windowBox.value.top, windowBox.value.height);
664
+ };
665
+ /**
666
+ * 有文档壳斜纹轨时,把刻度靠近轨道内侧并留出间距;否则清空偏移,沿用正文边缘定位。
667
+ */
668
+ const updateInlineAnchor = () => {
669
+ const root = indicatorRoot.value;
670
+ if (!root) {
671
+ inlineAnchor.value = void 0;
672
+ return;
491
673
  }
492
- if (activeIndex.value !== nextIndex) {
493
- activeIndex.value = nextIndex;
494
- followActiveMarker(nextIndex);
674
+ const layoutRail = root.closest(LAYOUT_SELECTOR)?.querySelector(resolvedPosition.value === "left" ? RAIL_START_SELECTOR : RAIL_END_SELECTOR);
675
+ const railRect = layoutRail && getComputedStyle(layoutRail).display !== "none" ? layoutRail.getBoundingClientRect() : void 0;
676
+ inlineAnchor.value = getIndicatorInlineOffset(root.getBoundingClientRect(), railRect, resolvedPosition.value, viewport.value?.offsetWidth || VIEWPORT_WIDTH);
677
+ };
678
+ /** 同步高度封顶、斜纹轨锚点、刻度比例和可视窗口。 */
679
+ const updateLayout = () => {
680
+ updateCappedHeight();
681
+ updateInlineAnchor();
682
+ if (props.target && markers.value.length) {
683
+ const article = props.target;
684
+ markers.value = markers.value.map((marker) => ({
685
+ ...marker,
686
+ ratio: getBlockRatio(marker.element, article)
687
+ }));
495
688
  }
689
+ updateWindow();
496
690
  };
497
- /** 将连续滚动事件合并到浏览器的下一绘制帧。 */
498
- const handleScroll = () => {
499
- if (!activeFrame) activeFrame = requestAnimationFrame(updateActiveIndex);
691
+ /** 跟随滚动容器、正文和指示器自身的尺寸变化。 */
692
+ const observeHostSize = () => {
693
+ hostResizeObserver?.disconnect();
694
+ hostResizeObserver = void 0;
695
+ if (typeof ResizeObserver !== "undefined") {
696
+ hostResizeObserver = new ResizeObserver(updateLayout);
697
+ const host = usesParentScroller ? parentScroller?.wrapper : scrollHost instanceof HTMLElement ? scrollHost : void 0;
698
+ if (host) hostResizeObserver.observe(host);
699
+ if (props.target) hostResizeObserver.observe(props.target);
700
+ if (viewport.value) hostResizeObserver.observe(viewport.value);
701
+ if (indicatorRoot.value) hostResizeObserver.observe(indicatorRoot.value);
702
+ const parent = indicatorRoot.value?.parentElement;
703
+ if (parent) hostResizeObserver.observe(parent);
704
+ const layout = indicatorRoot.value?.closest(LAYOUT_SELECTOR);
705
+ if (layout instanceof HTMLElement) hostResizeObserver.observe(layout);
706
+ const startRail = layout?.querySelector(RAIL_START_SELECTOR);
707
+ const endRail = layout?.querySelector(RAIL_END_SELECTOR);
708
+ if (startRail instanceof HTMLElement) hostResizeObserver.observe(startRail);
709
+ if (endRail instanceof HTMLElement) hostResizeObserver.observe(endRail);
710
+ }
711
+ updateLayout();
500
712
  };
501
- /** 从最新渲染的 Markdown DOM 重建文档刻度。 */
713
+ /**
714
+ * 计算文档块相对正文顶部的比例,用于铺在固定高度轨道上。
715
+ * @param element 当前文档块。
716
+ * @param article 正文根节点。
717
+ * @returns 0–1 的纵向比例。
718
+ */
719
+ const getBlockRatio = (element, article) => {
720
+ const articleHeight = article.offsetHeight || article.getBoundingClientRect().height;
721
+ if (articleHeight <= 0) return 0;
722
+ const top = element.getBoundingClientRect().top - article.getBoundingClientRect().top;
723
+ return Math.min(1, Math.max(0, top / articleHeight));
724
+ };
725
+ /** 从最新渲染的 Markdown DOM 重建小地图刻度。 */
502
726
  const refreshMarkers = () => {
503
727
  refreshFrame = 0;
504
728
  const target = props.target;
@@ -506,67 +730,28 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
506
730
  markers.value = [];
507
731
  return;
508
732
  }
509
- const elements = [...target.querySelectorAll(BLOCK_SELECTOR)].filter((element) => !isNestedBlock(element));
510
- let sectionTitle = t("markdown.indicator.document");
511
- markers.value = elements.map((element, index) => {
512
- const isHeading = /^H[1-6]$/.test(element.tagName);
513
- if (isHeading) sectionTitle = getHeadingTitle(element);
514
- const nextElement = elements[index + 1];
515
- const content = isHeading && nextElement && !/^H[1-6]$/.test(nextElement.tagName) ? getBlockText(nextElement) : isHeading ? "" : getBlockText(element);
516
- return {
517
- ariaLabel: getAriaLabel(sectionTitle, content),
518
- content,
519
- element,
520
- id: `${index}-${element.id || element.tagName}`,
521
- title: sectionTitle
522
- };
523
- });
733
+ const headings = collectMarkdownHeadings(target);
734
+ markers.value = headings.map((heading, index) => ({
735
+ content: getPreviewContent(heading.element, headings[index + 1]?.element),
736
+ element: heading.element,
737
+ id: heading.id || `${index}`,
738
+ isHeading: true,
739
+ ratio: getBlockRatio(heading.element, target),
740
+ title: heading.text
741
+ }));
524
742
  hoverIndex.value = void 0;
525
- updateActiveIndex();
526
- followActiveMarker(activeIndex.value);
743
+ updateWindow();
527
744
  };
528
745
  /** 合并 MutationObserver 的密集通知,避免 Playground 挂载时重复扫描。 */
529
746
  const scheduleRefresh = () => {
530
747
  if (!refreshFrame) refreshFrame = requestAnimationFrame(refreshMarkers);
531
748
  };
532
- /**
533
- * 读取当前滚动容器的可视高度。
534
- * sticky 的百分比 top 会相对整篇文档计算,不能用来做容器垂直居中。
535
- * @returns 滚动容器 clientHeight,窗口滚动时回退到 innerHeight。
536
- */
537
- const getScrollHostClientHeight = () => {
538
- if (usesParentScroller) return parentScroller?.wrapper?.clientHeight || parentScroller?.clientHeight || window.innerHeight;
539
- if (scrollHost instanceof HTMLElement) return scrollHost.clientHeight;
540
- return window.innerHeight;
541
- };
542
- /**
543
- * 读取指示器可视高度:数字配置优先,否则测量已挂载节点或回退默认值。
544
- * @returns 指示器高度,单位 px。
545
- */
546
- const resolveIndicatorHeight = () => {
547
- const height = props.options.height;
548
- if (typeof height === "number" && Number.isFinite(height) && height > 0) return height;
549
- const measured = viewport.value?.getBoundingClientRect().height || 0;
550
- if (measured > 0) return measured;
551
- return Math.min(window.innerHeight * .72, 600);
552
- };
553
- /** 把指示器钉在滚动容器可视区域的垂直中心。 */
554
- const updateStickyInset = () => {
555
- const hostHeight = getScrollHostClientHeight();
556
- const indicatorHeight = resolveIndicatorHeight();
557
- stickyInset.value = hostHeight > 0 ? `${Math.max(0, (hostHeight - indicatorHeight) / 2)}px` : "0px";
558
- };
559
- /** 跟随滚动容器和指示器自身的尺寸变化,保持垂直居中。 */
560
- const observeHostSize = () => {
561
- hostResizeObserver?.disconnect();
562
- hostResizeObserver = void 0;
563
- if (typeof ResizeObserver !== "undefined") {
564
- hostResizeObserver = new ResizeObserver(updateStickyInset);
565
- const host = usesParentScroller ? parentScroller?.wrapper : scrollHost instanceof HTMLElement ? scrollHost : void 0;
566
- if (host) hostResizeObserver.observe(host);
567
- if (viewport.value) hostResizeObserver.observe(viewport.value);
568
- }
569
- updateStickyInset();
749
+ /** 将连续滚动事件合并到下一帧:更新可视窗口,并按父级底部收缩高度。 */
750
+ const handleScroll = () => {
751
+ if (!windowFrame) windowFrame = requestAnimationFrame(() => {
752
+ updateCappedHeight();
753
+ updateWindow();
754
+ });
570
755
  };
571
756
  /** 解除旧文档的观察和滚动监听。 */
572
757
  const cleanupTarget = () => {
@@ -574,7 +759,7 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
574
759
  observer = void 0;
575
760
  hostResizeObserver?.disconnect();
576
761
  hostResizeObserver = void 0;
577
- window.removeEventListener("resize", updateStickyInset);
762
+ window.removeEventListener("resize", updateLayout);
578
763
  if (usesParentScroller) parentScroller?.off?.(handleScroll);
579
764
  else if (scrollHost) scrollHost.removeEventListener("scroll", handleScroll);
580
765
  usesParentScroller = false;
@@ -598,7 +783,7 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
598
783
  scrollHost = getScrollHost();
599
784
  scrollHost.addEventListener("scroll", handleScroll, { passive: true });
600
785
  }
601
- window.addEventListener("resize", updateStickyInset, { passive: true });
786
+ window.addEventListener("resize", updateLayout, { passive: true });
602
787
  observer = new MutationObserver(scheduleRefresh);
603
788
  observer.observe(props.target, {
604
789
  childList: true,
@@ -609,18 +794,16 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
609
794
  observeHostSize();
610
795
  };
611
796
  /**
612
- * 将指示器内的纵坐标转换为最近的文档块序号。
613
- * @param event 当前指针事件。
614
- * @returns 距离指针最近的文档块序号。
797
+ * 把轨道上的指针位置映射到最近的刻度,只用于悬停预览。
798
+ * @param ratio 轨道 0–1 比例。
799
+ * @returns 最近刻度序号。
615
800
  */
616
- const getIndexByPointer = (event) => {
617
- if (!rail.value || !markers.value.length) return 0;
618
- const elements = [...rail.value.querySelectorAll(".docs-markdown-indicator__marker")];
801
+ const getIndexByRatio = (ratio) => {
802
+ if (!markers.value.length) return 0;
619
803
  let closestIndex = 0;
620
804
  let closestDistance = Number.POSITIVE_INFINITY;
621
- elements.forEach((element, index) => {
622
- const bounds = element.getBoundingClientRect();
623
- const distance = Math.abs(event.clientY - (bounds.top + bounds.height / 2));
805
+ markers.value.forEach((marker, index) => {
806
+ const distance = Math.abs(marker.ratio - ratio);
624
807
  if (distance < closestDistance) {
625
808
  closestIndex = index;
626
809
  closestDistance = distance;
@@ -629,66 +812,73 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
629
812
  return closestIndex;
630
813
  };
631
814
  /**
632
- * 让摘要跟随当前刻度,同时限制在指示器可视高度内。
633
- * @param index 当前刻度序号。
815
+ * 读取指针相对轨道的比例。
816
+ * @param event 当前指针事件。
817
+ * @returns 0–1 比例。
818
+ */
819
+ const getPointerRatio = (event) => {
820
+ const bounds = rail.value?.getBoundingClientRect();
821
+ if (!bounds) return 0;
822
+ return getMinimapPointerRatio(event.clientY, bounds.top, bounds.height);
823
+ };
824
+ /**
825
+ * 让摘要跟随指针,同时限制在指示器可视高度内。
634
826
  * @param clientY 指针的视口纵坐标。
635
827
  */
636
- const updatePreviewPosition = (index, clientY) => {
828
+ const updatePreviewPosition = (clientY) => {
637
829
  const rootBounds = indicatorRoot.value?.getBoundingClientRect();
638
830
  const viewportBounds = viewport.value?.getBoundingClientRect();
639
- const marker = rail.value?.querySelectorAll(".docs-markdown-indicator__marker")[index];
640
- if (!rootBounds || !viewportBounds || !marker) return;
641
- const markerBounds = marker.getBoundingClientRect();
642
- const relativeY = (clientY ?? markerBounds.top + markerBounds.height / 2) - rootBounds.top;
643
- previewTop.value = Math.min(viewportBounds.height - 48, Math.max(48, relativeY));
831
+ if (!rootBounds || !viewportBounds) return;
832
+ previewTop.value = Math.min(viewportBounds.height - 48, Math.max(48, clientY - rootBounds.top));
644
833
  };
645
834
  /**
646
- * 滚动到指定文档块;拖动使用即时定位,点击使用平滑定位。
647
- * @param index 目标文档块序号。
835
+ * 按轨道比例滚动正文,不改 hash。
836
+ * @param ratio 轨道 0–1 比例。
648
837
  * @param behavior 滚动行为。
649
838
  */
650
- const scrollToMarker = (index, behavior) => {
651
- const marker = markers.value[index];
652
- if (!marker || !scrollHost) return;
653
- const markerTop = marker.element.getBoundingClientRect().top;
654
- if (usesParentScroller && parentScroller?.setScrollTop) parentScroller.setScrollTop((parentScroller.scrollTop || 0) + markerTop - getScrollHostTop() - 24);
655
- else if (scrollHost instanceof HTMLElement) {
656
- const hostTop = scrollHost.getBoundingClientRect().top;
839
+ const scrollToRatio = (ratio, behavior) => {
840
+ const metrics = getScrollMetrics();
841
+ const nextTop = ratio * Math.max(0, metrics.scrollHeight - metrics.clientHeight);
842
+ if (usesParentScroller && parentScroller?.setScrollTop) {
843
+ parentScroller.setScrollTop(nextTop);
844
+ return;
845
+ }
846
+ if (scrollHost instanceof HTMLElement) {
657
847
  scrollHost.scrollTo({
658
- top: scrollHost.scrollTop + markerTop - hostTop - 24,
659
- behavior
848
+ behavior,
849
+ top: nextTop
660
850
  });
661
- } else window.scrollTo({
662
- top: window.scrollY + markerTop - 24,
663
- behavior
851
+ return;
852
+ }
853
+ window.scrollTo({
854
+ behavior,
855
+ top: nextTop
664
856
  });
665
- activeIndex.value = index;
666
- followActiveMarker(index);
667
857
  };
668
858
  /**
669
- * 指针移动时展示摘要;按下拖动时同步快速浏览文档。
859
+ * 指针移动时展示摘要;按下拖动时按比例 scrub 正文。
670
860
  * @param event 当前指针事件。
671
861
  */
672
862
  const handlePointerMove = (event) => {
673
- const index = getIndexByPointer(event);
674
- hoverIndex.value = index;
675
- updatePreviewPosition(index, event.clientY);
676
- if (dragging.value) scrollToMarker(index, "auto");
863
+ const ratio = getPointerRatio(event);
864
+ hoverIndex.value = getIndexByRatio(ratio);
865
+ updatePreviewPosition(event.clientY);
866
+ if (dragging.value) scrollToRatio(ratio, "auto");
677
867
  };
678
868
  /**
679
- * 开始拖动指示器并定位到按下位置。
869
+ * 开始 scrub:点击即按比例定位,可拖动时再捕获指针。
680
870
  * @param event 当前指针事件。
681
871
  */
682
872
  const handlePointerDown = (event) => {
683
873
  if (event.button !== 0) return;
874
+ const ratio = getPointerRatio(event);
875
+ hoverIndex.value = getIndexByRatio(ratio);
876
+ updatePreviewPosition(event.clientY);
877
+ scrollToRatio(ratio, "auto");
684
878
  dragging.value = props.options.draggable !== false;
685
- const index = getIndexByPointer(event);
686
- hoverIndex.value = index;
687
- updatePreviewPosition(index, event.clientY);
688
879
  if (dragging.value) {
689
880
  captureTarget = event.currentTarget;
690
881
  captureTarget.setPointerCapture?.(event.pointerId);
691
- scrollToMarker(index, "auto");
692
882
  event.preventDefault();
693
883
  }
694
884
  };
@@ -706,96 +896,99 @@ var indicator_default = /* @__PURE__ */ (0, vue.defineComponent)({
706
896
  if (!dragging.value) hoverIndex.value = void 0;
707
897
  };
708
898
  /**
709
- * 点击单条刻度时平滑定位到对应文档块。
710
- * @param index 目标文档块序号。
711
- */
712
- const handleClick = (index) => {
713
- hoverIndex.value = index;
714
- updatePreviewPosition(index);
715
- scrollToMarker(index, "smooth");
716
- };
717
- /**
718
- * 计算刻度位置、基础宽度和靠近指针时的鱼眼展开宽度。
899
+ * 计算刻度在轨道上的位置、标题/正文宽度,以及靠近指针时的鱼眼展开。
719
900
  * @param index 当前刻度序号。
720
901
  * @returns 刻度的定位和宽度样式。
721
902
  */
722
903
  const getMarkerStyle = (index) => {
904
+ const marker = markers.value[index];
723
905
  const distance = typeof hoverIndex.value === "number" ? Math.abs(index - hoverIndex.value) : Number.POSITIVE_INFINITY;
724
- return { width: `${distance <= 3 ? [
725
- 28,
726
- 22,
727
- 16,
728
- 10
729
- ][distance] : 8}px` };
906
+ const base = marker?.isHeading ? 10 : 6;
907
+ const expandedWidths = [
908
+ base + 6,
909
+ base + 4,
910
+ base + 2,
911
+ base + 1
912
+ ];
913
+ const hoverWidth = distance <= 3 ? expandedWidths[distance] : base;
914
+ const activeWidth = index === activeIndex.value ? base + 8 : base;
915
+ return {
916
+ top: `${(marker?.ratio || 0) * 100}%`,
917
+ width: `${Math.max(hoverWidth, activeWidth)}px`
918
+ };
730
919
  };
731
- (0, vue.watch)([() => props.target, localeName], setupTarget, { immediate: true });
732
- (0, vue.watch)([() => props.options.height, viewport], () => {
920
+ (0, vue.watch)([() => props.target, localeName], setupTarget, {
921
+ immediate: true,
922
+ flush: "post"
923
+ });
924
+ (0, vue.watch)(indicatorRoot, (root) => {
925
+ if (root) observeHostSize();
926
+ }, { flush: "post" });
927
+ (0, vue.watch)([
928
+ () => props.options.height,
929
+ () => props.options.position,
930
+ viewport
931
+ ], () => {
733
932
  if (viewport.value && hostResizeObserver) hostResizeObserver.observe(viewport.value);
734
- updateStickyInset();
933
+ updateLayout();
735
934
  });
736
935
  (0, vue.onBeforeUnmount)(() => {
737
936
  targetGeneration++;
738
937
  cleanupTarget();
739
938
  if (refreshFrame) cancelAnimationFrame(refreshFrame);
740
- if (activeFrame) cancelAnimationFrame(activeFrame);
939
+ if (windowFrame) cancelAnimationFrame(windowFrame);
741
940
  });
742
941
  return (_ctx, _cache) => {
743
- return markers.value.length > 1 ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("aside", {
942
+ return markers.value.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("aside", {
744
943
  key: 0,
745
944
  ref_key: "indicatorRoot",
746
945
  ref: indicatorRoot,
747
- class: (0, vue.normalizeClass)(["docs-markdown-indicator", `is-${__props.options.position || "right"}`]),
946
+ class: (0, vue.normalizeClass)(["docs-markdown-indicator", `is-${resolvedPosition.value}`]),
748
947
  style: (0, vue.normalizeStyle)(rootStyle.value),
749
948
  "aria-label": (0, vue.unref)(t)("markdown.indicator.label")
750
949
  }, [(0, vue.createElementVNode)("div", {
751
950
  ref_key: "viewport",
752
951
  ref: viewport,
753
- class: "docs-markdown-indicator__viewport",
952
+ class: (0, vue.normalizeClass)(["docs-markdown-indicator__viewport", { "is-dragging": dragging.value }]),
953
+ role: "slider",
954
+ "aria-orientation": "vertical",
955
+ "aria-valuemin": 0,
956
+ "aria-valuemax": 100,
957
+ "aria-valuenow": scrollProgress.value,
958
+ "aria-valuetext": `${scrollProgress.value}%`,
959
+ style: (0, vue.normalizeStyle)(viewportStyle.value),
754
960
  onPointermove: handlePointerMove,
755
961
  onPointerleave: handlePointerLeave,
756
962
  onPointerdown: handlePointerDown,
757
963
  onPointerup: handlePointerUp,
758
964
  onPointercancel: handlePointerUp
759
- }, [(0, vue.createVNode)((0, vue.unref)(_deot_vc.Scroller), {
760
- ref_key: "indicatorScroller",
761
- ref: indicatorScroller,
762
- class: "docs-markdown-indicator__scroller",
763
- "auto-resize": true,
764
- native: false,
765
- "show-bar": true,
766
- height: "100%",
767
- "wrapper-style": "overflow-x: hidden;"
768
- }, {
769
- default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", {
770
- ref_key: "rail",
771
- ref: rail,
772
- class: "docs-markdown-indicator__rail"
773
- }, [(0, vue.createElementVNode)("div", _hoisted_2, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(markers.value, (marker, index) => {
774
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
775
- key: marker.id,
776
- type: "button",
777
- class: (0, vue.normalizeClass)(["docs-markdown-indicator__marker", {
778
- "is-active": index === activeIndex.value,
779
- "is-hovered": index === hoverIndex.value
780
- }]),
781
- style: (0, vue.normalizeStyle)(getMarkerStyle(index)),
782
- "aria-label": marker.ariaLabel,
783
- onClick: ($event) => handleClick(index)
784
- }, null, 14, _hoisted_3);
785
- }), 128))])], 512)]),
786
- _: 1
787
- }, 512)], 544), __props.options.preview !== false && hoveredMarker.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
965
+ }, [(0, vue.createElementVNode)("div", {
966
+ ref_key: "rail",
967
+ ref: rail,
968
+ class: "docs-markdown-indicator__rail"
969
+ }, [(0, vue.createElementVNode)("div", {
970
+ class: "docs-markdown-indicator__window",
971
+ style: (0, vue.normalizeStyle)(windowStyle.value)
972
+ }, null, 4), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(markers.value, (marker, index) => {
973
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
974
+ key: marker.id,
975
+ class: (0, vue.normalizeClass)(["docs-markdown-indicator__marker", {
976
+ "is-heading": marker.isHeading,
977
+ "is-hovered": index === hoverIndex.value,
978
+ "is-active": index === activeIndex.value,
979
+ "is-in-view": isMarkerInView(index)
980
+ }]),
981
+ style: (0, vue.normalizeStyle)(getMarkerStyle(index))
982
+ }, null, 6);
983
+ }), 128))], 512)], 46, _hoisted_2), __props.options.preview !== false && !dragging.value && hoveredMarker.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
788
984
  key: 0,
789
985
  class: "docs-markdown-indicator__preview",
790
986
  style: (0, vue.normalizeStyle)(previewStyle.value)
791
- }, [(0, vue.createElementVNode)("div", _hoisted_4, (0, vue.toDisplayString)(hoveredMarker.value.title), 1), hoveredMarker.value.content ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_5, (0, vue.toDisplayString)(hoveredMarker.value.content), 1)) : (0, vue.createCommentVNode)("v-if", true)], 4)) : (0, vue.createCommentVNode)("v-if", true)], 14, _hoisted_1$1)) : (0, vue.createCommentVNode)("v-if", true);
987
+ }, [(0, vue.createElementVNode)("div", _hoisted_3, (0, vue.toDisplayString)(hoveredMarker.value.title), 1), hoveredMarker.value.content ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_4, (0, vue.toDisplayString)(hoveredMarker.value.content), 1)) : (0, vue.createCommentVNode)("v-if", true)], 4)) : (0, vue.createCommentVNode)("v-if", true)], 14, _hoisted_1)) : (0, vue.createCommentVNode)("v-if", true);
792
988
  };
793
989
  }
794
990
  });
795
991
  //#endregion
796
- //#region packages/markdown/src/wrapper.vue?vue&type=script&setup=true&lang.ts
797
- var _hoisted_1 = { class: "docs-markdown" };
798
- //#endregion
799
992
  //#region packages/markdown/src/wrapper.vue
800
993
  var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
801
994
  __name: "wrapper",
@@ -805,6 +998,8 @@ var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
805
998
  default: true
806
999
  },
807
1000
  locale: {},
1001
+ theme: { default: "default" },
1002
+ playground: { default: () => ({}) },
808
1003
  modelValue: {},
809
1004
  value: {}
810
1005
  },
@@ -819,13 +1014,15 @@ var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
819
1014
  if (props.indicator === false) return void 0;
820
1015
  return typeof props.indicator === "object" ? props.indicator : {};
821
1016
  });
1017
+ const resolvedTheme = (0, vue.computed)(() => props.theme === "traditional" ? "traditional" : "default");
822
1018
  const source = (0, vue.computed)(() => typeof props.modelValue === "string" ? props.modelValue : props.value);
823
1019
  const markdownBinding = (0, vue.computed)(() => ({
824
1020
  source: source.value,
825
- locale: locale.value
1021
+ locale: locale.value,
1022
+ playground: props.playground
826
1023
  }));
827
1024
  return (_ctx, _cache) => {
828
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1, [indicatorOptions.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(indicator_default, {
1025
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", { class: (0, vue.normalizeClass)(["docs-markdown", `docs-markdown--${resolvedTheme.value}`]) }, [indicatorOptions.value && content.value ? ((0, vue.openBlock)(), (0, vue.createBlock)(indicator_default, {
829
1026
  key: 0,
830
1027
  target: content.value,
831
1028
  options: indicatorOptions.value
@@ -833,10 +1030,12 @@ var wrapper_default = /* @__PURE__ */ (0, vue.defineComponent)({
833
1030
  ref_key: "content",
834
1031
  ref: content,
835
1032
  class: "docs-markdown-reset"
836
- }, null, 512), [[(0, vue.unref)(vMarkdown), markdownBinding.value]])]);
1033
+ }, null, 512), [[(0, vue.unref)(vMarkdown), markdownBinding.value]])], 2);
837
1034
  };
838
1035
  }
839
1036
  });
840
1037
  //#endregion
841
1038
  exports.Markdown = wrapper_default;
1039
+ exports.collectMarkdownHeadings = collectMarkdownHeadings;
1040
+ exports.getMarkdownHeadingTitle = getMarkdownHeadingTitle;
842
1041
  exports.parseMarkdownSearchSections = parseMarkdownSearchSections;