vue-stream-markdown 0.1.2 → 0.1.4

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.
Files changed (40) hide show
  1. package/README.md +8 -0
  2. package/dist/button-D6ccVxGm.js +5 -0
  3. package/dist/{button-vKhxUL3C.js → button-jFTF378X.js} +1 -1
  4. package/dist/{code-_dL6Qk0F.js → code-BxWvKFxG.js} +8 -8
  5. package/dist/code-block-B72JfFy-.js +9 -0
  6. package/dist/{code-block-wYeMwBck.js → code-block-uZcUI59D.js} +40 -29
  7. package/dist/{composables-CZ7YmjNl.js → composables-Cx0nvyHl.js} +124 -25
  8. package/dist/dropdown-oiCw8QVq.js +5 -0
  9. package/dist/error-component-8y13-fYG.js +4 -0
  10. package/dist/{error-component-ChEpOmSX.js → error-component-Bhl770lu.js} +4 -3
  11. package/dist/{image-DB-4Sv8R.js → image-BDtPwitR.js} +6 -5
  12. package/dist/index.css +58 -73
  13. package/dist/index.d.ts +112 -53
  14. package/dist/index.js +76 -19
  15. package/dist/{inline-math-rChhv_1j.js → inline-math-CAqboT7h.js} +12 -15
  16. package/dist/{link-CJk67Kbh.js → link-C8BSlPhZ.js} +2 -2
  17. package/dist/{math-ZLxqHT_f.js → math-CT1TB2pK.js} +12 -15
  18. package/dist/{mermaid-CNFU7Pg8.js → mermaid-CYrA8rCl.js} +5 -5
  19. package/dist/{previewers-CMreQ4TA.js → previewers-ByaS2Ksa.js} +1 -1
  20. package/dist/segmented-B4IEw3wg.js +6 -0
  21. package/dist/{segmented-RoWxDIP3.js → segmented-DBW9ignu.js} +1 -1
  22. package/dist/{shiki-4529WbJJ.js → shiki-bQYWLdW3.js} +9 -14
  23. package/dist/{renderer-CZ41eK_V.js → shiki-token-renderer-D164wC7Y.js} +10 -23
  24. package/dist/{table-BmGwZIPR.js → table-l_3z210v.js} +6 -5
  25. package/dist/{tooltip-KcTe6tBF.js → tooltip-3UDC9mw-.js} +1 -1
  26. package/dist/tooltip-CAoc_U1X.js +4 -0
  27. package/dist/vanilla-CFWVwBvD.js +39 -0
  28. package/dist/vanilla-De7W3PpS.js +4 -0
  29. package/dist/zoom-container-DBgGdbJS.js +6 -0
  30. package/dist/{zoom-container-D1Mys9gv.js → zoom-container-gjQ0EMyk.js} +18 -5
  31. package/package.json +1 -1
  32. package/dist/button-DBgeYAZy.js +0 -5
  33. package/dist/code-block-DGZ48hb0.js +0 -9
  34. package/dist/dropdown-DR-uQ215.js +0 -5
  35. package/dist/error-component-CKCT9gXy.js +0 -4
  36. package/dist/segmented-63wrfRNo.js +0 -6
  37. package/dist/tooltip--rusT8LZ.js +0 -4
  38. package/dist/vanilla-CA9QO96X.js +0 -3
  39. package/dist/vanilla-DkX6g0dm.js +0 -87
  40. package/dist/zoom-container-C-CB2LTY.js +0 -6
package/README.md CHANGED
@@ -66,6 +66,14 @@ This project also uses and benefits from:
66
66
  - [KaTeX](https://katex.org/) - Fast math typesetting library for the web
67
67
  - [Remend](https://github.com/vercel/streamdown/tree/main/packages/remend) - Intelligently parses and styles incomplete Markdown blocks
68
68
 
69
+ ### Code Sources
70
+ - [markstream-vue](https://github.com/Simon-He95/markstream-vue) - The original inspiration for learning AST-based custom markdown rendering, and the source of the animation implementation used in this project
71
+ - [ast-explorer](https://github.com/sxzz/ast-explorer) - Learned AST knowledge from this project, and the playground layout inspiration and AST syntax tree filtering code are derived from it
72
+
73
+ ## Acknowledgments
74
+
75
+ I would like to express my sincere gratitude to those who provided guidance and support during the project selection phase and promotion phase of this project. Without their encouragement and support, I would not have been able to complete this work. In particular, the [streamdown](https://streamdown.ai/) community provided excellent code guidance and even helped fix several issues.
76
+
69
77
  ## Troubleshooting
70
78
 
71
79
  The playground supports generating shareable links and provides streaming controls (forward/backward navigation) for debugging streaming rendering issues.
@@ -0,0 +1,5 @@
1
+ import "./composables-Cx0nvyHl.js";
2
+ import "./tooltip-3UDC9mw-.js";
3
+ import { t as button_default } from "./button-jFTF378X.js";
4
+
5
+ export { button_default as default };
@@ -1,4 +1,4 @@
1
- import { t as tooltip_default } from "./tooltip-KcTe6tBF.js";
1
+ import { t as tooltip_default } from "./tooltip-3UDC9mw-.js";
2
2
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, h, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, withCtx } from "vue";
3
3
 
4
4
  //#region src/components/dropdown.vue?vue&type=script&setup=true&lang.ts
@@ -1,10 +1,10 @@
1
- import "./previewers-CMreQ4TA.js";
2
- import { i as useShiki } from "./composables-CZ7YmjNl.js";
3
- import "./tooltip-KcTe6tBF.js";
4
- import "./button-vKhxUL3C.js";
1
+ import "./previewers-ByaS2Ksa.js";
2
+ import { i as useShiki } from "./composables-Cx0nvyHl.js";
3
+ import "./tooltip-3UDC9mw-.js";
4
+ import "./button-jFTF378X.js";
5
5
  import "./modal-CuQR21UD.js";
6
- import { t as code_block_default } from "./code-block-wYeMwBck.js";
7
- import "./segmented-RoWxDIP3.js";
6
+ import { t as code_block_default } from "./code-block-uZcUI59D.js";
7
+ import "./segmented-DBW9ignu.js";
8
8
  import { computed, createBlock, createCommentVNode, defineAsyncComponent, defineComponent, mergeProps, normalizeProps, openBlock, resolveDynamicComponent, withCtx } from "vue";
9
9
 
10
10
  //#region src/components/renderers/code/index.vue?vue&type=script&setup=true&lang.ts
@@ -82,8 +82,8 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
82
82
  const languageClass = computed(() => `language-${props.node.lang}`);
83
83
  const { installed: hasShiki } = useShiki();
84
84
  const components = {
85
- vanilla: defineAsyncComponent(() => import("./vanilla-CA9QO96X.js")),
86
- shiki: defineAsyncComponent(() => import("./shiki-4529WbJJ.js"))
85
+ vanilla: defineAsyncComponent(() => import("./vanilla-De7W3PpS.js")),
86
+ shiki: defineAsyncComponent(() => import("./shiki-bQYWLdW3.js"))
87
87
  };
88
88
  const component = computed(() => {
89
89
  if (hasShiki.value) return components.shiki;
@@ -0,0 +1,9 @@
1
+ import "./previewers-ByaS2Ksa.js";
2
+ import "./composables-Cx0nvyHl.js";
3
+ import "./tooltip-3UDC9mw-.js";
4
+ import "./button-jFTF378X.js";
5
+ import "./modal-CuQR21UD.js";
6
+ import { t as code_block_default } from "./code-block-uZcUI59D.js";
7
+ import "./segmented-DBW9ignu.js";
8
+
9
+ export { code_block_default as default };
@@ -1,8 +1,8 @@
1
- import { t as CODE_PREVIEWERS } from "./previewers-CMreQ4TA.js";
2
- import { B as useContext, I as LANGUAGE_ALIAS, L as LANGUAGE_EXTENSIONS, M as ICONS, O as save, R as LANGUAGE_ICONS, a as useMermaid, u as useI18n, z as useControls } from "./composables-CZ7YmjNl.js";
3
- import { t as button_default } from "./button-vKhxUL3C.js";
1
+ import { t as CODE_PREVIEWERS } from "./previewers-ByaS2Ksa.js";
2
+ import { B as useContext, I as LANGUAGE_ALIAS, L as LANGUAGE_EXTENSIONS, O as save, R as LANGUAGE_ICONS, V as useCodeOptions, a as useMermaid, u as useI18n, z as useControls } from "./composables-Cx0nvyHl.js";
3
+ import { t as button_default } from "./button-jFTF378X.js";
4
4
  import { t as modal_default } from "./modal-CuQR21UD.js";
5
- import { t as segmented_default } from "./segmented-RoWxDIP3.js";
5
+ import { t as segmented_default } from "./segmented-DBW9ignu.js";
6
6
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineAsyncComponent, defineComponent, mergeProps, normalizeClass, normalizeProps, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, toRefs, unref, useModel, vShow, watch, withCtx, withDirectives } from "vue";
7
7
  import { useClipboard } from "@vueuse/core";
8
8
 
@@ -104,14 +104,15 @@ var preview_segmented_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
104
104
  const mode = useModel(__props, "mode");
105
105
  const collapsed = useModel(__props, "collapsed");
106
106
  const { t } = useI18n();
107
+ const { icons } = useContext();
107
108
  const SEGMENTED_OPTIONS = computed(() => [{
108
109
  label: t("button.preview"),
109
110
  value: "preview",
110
- icon: ICONS.preview
111
+ icon: icons.value.preview
111
112
  }, {
112
113
  label: t("button.source"),
113
114
  value: "source",
114
- icon: ICONS.code
115
+ icon: icons.value.code
115
116
  }]);
116
117
  return (_ctx, _cache) => {
117
118
  return openBlock(), createBlock(segmented_default, {
@@ -204,11 +205,12 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
204
205
  },
205
206
  setup(__props) {
206
207
  const props = __props;
207
- const { controls, previewers } = toRefs(props);
208
+ const { controls, previewers, codeOptions } = toRefs(props);
208
209
  const { t } = useI18n();
210
+ const { icons } = useContext();
209
211
  const { isControlEnabled } = useControls({ controls });
210
212
  const { installed: hasMermaid } = useMermaid();
211
- const CodeNode = defineAsyncComponent(() => import("./code-_dL6Qk0F.js"));
213
+ const CodeNode = defineAsyncComponent(() => import("./code-BxWvKFxG.js"));
212
214
  const { onCopied } = useContext();
213
215
  const { copy, copied } = useClipboard({ legacy: true });
214
216
  const { saveMermaid } = useMermaid();
@@ -221,30 +223,39 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
221
223
  if (LANGUAGE_ALIAS[lang]) return LANGUAGE_ALIAS[lang];
222
224
  return lang;
223
225
  });
224
- const showLanguageIcon = computed(() => {
225
- var _props$codeOptions;
226
- return typeof ((_props$codeOptions = props.codeOptions) === null || _props$codeOptions === void 0 ? void 0 : _props$codeOptions.languageIcon) === "boolean" ? props.codeOptions.languageIcon : true;
227
- });
228
- const showLanguageName = computed(() => {
229
- var _props$codeOptions2;
230
- return typeof ((_props$codeOptions2 = props.codeOptions) === null || _props$codeOptions2 === void 0 ? void 0 : _props$codeOptions2.languageName) === "boolean" ? props.codeOptions.languageName : true;
226
+ const { showLanguageIcon, showLanguageName } = useCodeOptions({
227
+ codeOptions,
228
+ language
231
229
  });
232
230
  const showLanguageTitle = computed(() => showLanguageIcon.value || showLanguageName.value);
233
231
  const showCollapse = computed(() => isControlEnabled("code.collapse"));
234
232
  const showCopy = computed(() => isControlEnabled("code.copy"));
235
233
  const showDownload = computed(() => isControlEnabled("code.download"));
236
234
  const showFullscreen = computed(() => isControlEnabled("code.fullscreen"));
237
- const icon = computed(() => LANGUAGE_ICONS[language.value] || LANGUAGE_ICONS.text);
235
+ const icon = computed(() => {
236
+ var _codeOptions$value;
237
+ const custom = (_codeOptions$value = codeOptions.value) === null || _codeOptions$value === void 0 || (_codeOptions$value = _codeOptions$value.language) === null || _codeOptions$value === void 0 || (_codeOptions$value = _codeOptions$value[language.value]) === null || _codeOptions$value === void 0 ? void 0 : _codeOptions$value.languageIcon;
238
+ if (typeof custom === "object") return custom;
239
+ return LANGUAGE_ICONS[language.value] || LANGUAGE_ICONS.text;
240
+ });
238
241
  const previewable = computed(() => {
239
242
  if (previewers.value === false) return false;
240
- const mermaid = language.value === "mermaid" && hasMermaid.value;
241
243
  const html = language.value === "html" && !props.node.loading;
244
+ const mermaid = language.value === "mermaid" && hasMermaid.value;
245
+ if (previewers.value === true) {
246
+ if (language.value === "html" && html) return true;
247
+ if (language.value === "mermaid" && mermaid) return true;
248
+ return false;
249
+ }
242
250
  if (typeof previewers.value === "object") {
243
- const _mermaid = previewers.value.mermaid !== false && mermaid;
244
- const _html = previewers.value.html !== false && html;
245
- return _mermaid || _html;
251
+ if (previewers.value[language.value] === false) return false;
252
+ if (language.value === "html" && html) return true;
253
+ if (language.value === "mermaid" && mermaid) return true;
254
+ const component = previewers.value[language.value];
255
+ if (typeof component === "object" && !props.node.loading) return !!component;
256
+ return false;
246
257
  }
247
- return mermaid || html;
258
+ return false;
248
259
  });
249
260
  const PreviewComponent = computed(() => {
250
261
  if (!previewers.value || typeof previewers.value === "boolean") return CODE_PREVIEWERS[language.value];
@@ -275,7 +286,7 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
275
286
  {
276
287
  name: t("button.collapse"),
277
288
  key: "collapse",
278
- icon: ICONS.collapse,
289
+ icon: icons.value.collapse,
279
290
  iconStyle: {
280
291
  transform: collapsed.value ? "rotate(180deg)" : void 0,
281
292
  transition: "transform var(--default-transition-duration)"
@@ -286,7 +297,7 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
286
297
  {
287
298
  name: t("button.copy"),
288
299
  key: "copy",
289
- icon: copied.value ? ICONS.check : ICONS.copy,
300
+ icon: copied.value ? icons.value.check : icons.value.copy,
290
301
  visible: () => showCopy.value,
291
302
  onClick: () => {
292
303
  if (!props.node.value) return;
@@ -297,7 +308,7 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
297
308
  {
298
309
  name: t("button.download"),
299
310
  key: "download",
300
- icon: ICONS.download,
311
+ icon: icons.value.download,
301
312
  options: downloadOptions.value.length > 0 ? downloadOptions.value : void 0,
302
313
  visible: () => showDownload.value && !!LANGUAGE_EXTENSIONS[language.value],
303
314
  onClick: (_event, item) => {
@@ -313,7 +324,7 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
313
324
  {
314
325
  name: fullscreen.value ? t("button.minimize") : t("button.maximize"),
315
326
  key: "fullscreen",
316
- icon: fullscreen.value ? ICONS.minimize : ICONS.maximize,
327
+ icon: fullscreen.value ? icons.value.minimize : icons.value.maximize,
317
328
  visible: () => showFullscreen.value,
318
329
  onClick: () => fullscreen.value = !fullscreen.value
319
330
  }
@@ -337,8 +348,8 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
337
348
  key: 0,
338
349
  icon: icon.value,
339
350
  language: language.value,
340
- "show-icon": showLanguageIcon.value,
341
- "show-name": showLanguageName.value
351
+ "show-icon": unref(showLanguageIcon),
352
+ "show-name": unref(showLanguageName)
342
353
  }, null, 8, [
343
354
  "icon",
344
355
  "language",
@@ -370,8 +381,8 @@ var index_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
370
381
  key: 0,
371
382
  icon: icon.value,
372
383
  language: language.value,
373
- "show-icon": showLanguageIcon.value,
374
- "show-name": showLanguageName.value
384
+ "show-icon": unref(showLanguageIcon),
385
+ "show-name": unref(showLanguageName)
375
386
  }, null, 8, [
376
387
  "icon",
377
388
  "language",
@@ -6,10 +6,37 @@ import "tippy.js/themes/light.css";
6
6
  import "tippy.js/dist/border.css";
7
7
  import "tippy.js/dist/svg-arrow.css";
8
8
 
9
+ //#region src/composables/use-code-options.ts
10
+ function useCodeOptions(options) {
11
+ const language = computed(() => unref(options.language) || "");
12
+ const codeOptions = computed(() => unref(options.codeOptions));
13
+ const languageCodeOptions = computed(() => {
14
+ var _codeOptions$value;
15
+ return ((_codeOptions$value = codeOptions.value) === null || _codeOptions$value === void 0 || (_codeOptions$value = _codeOptions$value.language) === null || _codeOptions$value === void 0 ? void 0 : _codeOptions$value[language.value]) ?? codeOptions.value;
16
+ });
17
+ return {
18
+ languageCodeOptions,
19
+ showLanguageIcon: computed(() => {
20
+ var _languageCodeOptions$;
21
+ return typeof ((_languageCodeOptions$ = languageCodeOptions.value) === null || _languageCodeOptions$ === void 0 ? void 0 : _languageCodeOptions$.languageIcon) === "boolean" ? languageCodeOptions.value.languageIcon : true;
22
+ }),
23
+ showLanguageName: computed(() => {
24
+ var _languageCodeOptions$2;
25
+ return typeof ((_languageCodeOptions$2 = languageCodeOptions.value) === null || _languageCodeOptions$2 === void 0 ? void 0 : _languageCodeOptions$2.languageName) === "boolean" ? languageCodeOptions.value.languageName : true;
26
+ }),
27
+ showLineNumbers: computed(() => {
28
+ var _languageCodeOptions$3;
29
+ return typeof ((_languageCodeOptions$3 = languageCodeOptions.value) === null || _languageCodeOptions$3 === void 0 ? void 0 : _languageCodeOptions$3.lineNumbers) === "boolean" ? languageCodeOptions.value.lineNumbers : true;
30
+ })
31
+ };
32
+ }
33
+
34
+ //#endregion
9
35
  //#region src/composables/use-context.ts
10
36
  const CONTEXT_KEY = Symbol("stream-markdown-context");
11
37
  function useContext() {
12
38
  const context = injectContext();
39
+ const icons = computed(() => unref(context.icons) ?? {});
13
40
  const isDark = computed(() => unref(context.isDark) ?? false);
14
41
  function provideContext(ctx) {
15
42
  provide(CONTEXT_KEY, {
@@ -24,6 +51,7 @@ function useContext() {
24
51
  context,
25
52
  provideContext,
26
53
  injectContext,
54
+ icons,
27
55
  isDark,
28
56
  get getContainer() {
29
57
  return context.getContainer || (() => void 0);
@@ -624,21 +652,6 @@ const SHADCN_SCHEMAS = [
624
652
 
625
653
  //#endregion
626
654
  //#region src/utils/harden.ts
627
- const safeProtocols = new Set([
628
- "https:",
629
- "http:",
630
- "irc:",
631
- "ircs:",
632
- "mailto:",
633
- "xmpp:",
634
- "blob:"
635
- ]);
636
- const blockedProtocols = new Set([
637
- "javascript:",
638
- "data:",
639
- "file:",
640
- "vbscript:"
641
- ]);
642
655
  function parseUrl(url, defaultOrigin) {
643
656
  if (typeof url !== "string") return null;
644
657
  try {
@@ -649,13 +662,33 @@ function parseUrl(url, defaultOrigin) {
649
662
  } catch {
650
663
  return null;
651
664
  }
665
+ if (url.startsWith("/") || url.startsWith("./") || url.startsWith("../")) try {
666
+ return new URL(url, "http://example.com");
667
+ } catch {
668
+ return null;
669
+ }
652
670
  return null;
653
671
  }
654
672
  }
655
673
  function isPathRelativeUrl(url) {
656
674
  if (typeof url !== "string") return false;
657
- return url.startsWith("/");
675
+ return url.startsWith("/") || url.startsWith("./") || url.startsWith("../");
658
676
  }
677
+ const safeProtocols = new Set([
678
+ "https:",
679
+ "http:",
680
+ "irc:",
681
+ "ircs:",
682
+ "mailto:",
683
+ "xmpp:",
684
+ "blob:"
685
+ ]);
686
+ const blockedProtocols = new Set([
687
+ "javascript:",
688
+ "data:",
689
+ "file:",
690
+ "vbscript:"
691
+ ]);
659
692
  function transformUrl(url, allowedPrefixes, defaultOrigin, allowDataImages = false, isImage = false, allowedProtocols = []) {
660
693
  if (!url) return null;
661
694
  if (typeof url === "string" && url.startsWith("#") && !isImage) try {
@@ -1397,7 +1430,11 @@ function useShiki(options) {
1397
1430
  const darkTheme = computed(() => shikiTheme.value[1] ?? DEFAULT_DARK_THEME);
1398
1431
  const langAlias = computed(() => {
1399
1432
  var _unref2;
1400
- return ((_unref2 = unref(options === null || options === void 0 ? void 0 : options.shikiOptions)) === null || _unref2 === void 0 ? void 0 : _unref2.langAlias) ?? {};
1433
+ const data = ((_unref2 = unref(options === null || options === void 0 ? void 0 : options.shikiOptions)) === null || _unref2 === void 0 ? void 0 : _unref2.langAlias) ?? {};
1434
+ return {
1435
+ ...LANGUAGE_ALIAS,
1436
+ ...data
1437
+ };
1401
1438
  });
1402
1439
  const codeToTokenOptions = computed(() => {
1403
1440
  var _unref3;
@@ -1415,7 +1452,7 @@ function useShiki(options) {
1415
1452
  return theme.id;
1416
1453
  }
1417
1454
  async function getLanguage() {
1418
- if (LANGUAGE_ALIAS[lang.value]) return LANGUAGE_ALIAS[lang.value];
1455
+ if (langAlias.value[lang.value]) return langAlias.value[lang.value];
1419
1456
  const { bundledLanguagesInfo } = await import("shiki");
1420
1457
  const language = bundledLanguagesInfo.find((l) => {
1421
1458
  var _l$aliases;
@@ -1443,10 +1480,7 @@ function useShiki(options) {
1443
1480
  return createHighlighter({
1444
1481
  themes: [await getTheme()],
1445
1482
  langs: [await getLanguage()],
1446
- langAlias: {
1447
- ...LANGUAGE_ALIAS,
1448
- ...langAlias.value
1449
- }
1483
+ langAlias: langAlias.value
1450
1484
  });
1451
1485
  })();
1452
1486
  highlighter = await createHighlighterPromise;
@@ -1561,6 +1595,8 @@ function useZoom(options = {}) {
1561
1595
  x: 0,
1562
1596
  y: 0
1563
1597
  });
1598
+ const isPinching = ref(false);
1599
+ const touchZoomState = ref(null);
1564
1600
  const transformStyle = computed(() => ({ transform: `translate(${translateX.value}px, ${translateY.value}px) scale(${zoom.value})` }));
1565
1601
  function zoomIn() {
1566
1602
  if (zoom.value < maxZoom) zoom.value = Math.min(zoom.value + zoomStep, maxZoom);
@@ -1577,6 +1613,7 @@ function useZoom(options = {}) {
1577
1613
  zoom.value = Math.min(Math.max(value, minZoom), maxZoom);
1578
1614
  }
1579
1615
  function startDrag(e) {
1616
+ if (isPinching.value) return;
1580
1617
  isDragging.value = true;
1581
1618
  dragStart.value = {
1582
1619
  x: e.clientX - translateX.value,
@@ -1587,7 +1624,7 @@ function useZoom(options = {}) {
1587
1624
  }
1588
1625
  }
1589
1626
  function onDrag(e) {
1590
- if (!isDragging.value) return;
1627
+ if (!isDragging.value || isPinching.value) return;
1591
1628
  translateX.value = e.clientX - dragStart.value.x;
1592
1629
  translateY.value = e.clientY - dragStart.value.y;
1593
1630
  }
@@ -1615,6 +1652,65 @@ function useZoom(options = {}) {
1615
1652
  }
1616
1653
  }
1617
1654
  }
1655
+ function getTouchDistance(touch1, touch2) {
1656
+ const dx = touch1.clientX - touch2.clientX;
1657
+ const dy = touch1.clientY - touch2.clientY;
1658
+ return Math.sqrt(dx * dx + dy * dy);
1659
+ }
1660
+ function handleTouchStart(event, containerElement) {
1661
+ if (event.touches.length === 2) {
1662
+ event.preventDefault();
1663
+ isPinching.value = true;
1664
+ const touch1 = event.touches[0];
1665
+ const touch2 = event.touches[1];
1666
+ const distance = getTouchDistance(touch1, touch2);
1667
+ const centerX = (touch1.clientX + touch2.clientX) / 2;
1668
+ const centerY = (touch1.clientY + touch2.clientY) / 2;
1669
+ const rect = containerElement.getBoundingClientRect();
1670
+ const containerCenterX = rect.width / 2;
1671
+ const containerCenterY = rect.height / 2;
1672
+ const offsetX = centerX - rect.left - containerCenterX;
1673
+ const offsetY = centerY - rect.top - containerCenterY;
1674
+ touchZoomState.value = {
1675
+ initialDistance: distance,
1676
+ initialZoom: zoom.value,
1677
+ initialTranslateX: translateX.value,
1678
+ initialTranslateY: translateY.value,
1679
+ centerX: offsetX,
1680
+ centerY: offsetY
1681
+ };
1682
+ }
1683
+ }
1684
+ function handleTouchMove(event, containerElement) {
1685
+ if (event.touches.length === 2 && touchZoomState.value) {
1686
+ event.preventDefault();
1687
+ const touch1 = event.touches[0];
1688
+ const touch2 = event.touches[1];
1689
+ const scale = getTouchDistance(touch1, touch2) / touchZoomState.value.initialDistance;
1690
+ const newZoom = touchZoomState.value.initialZoom * scale;
1691
+ const clampedZoom = Math.min(Math.max(newZoom, minZoom), maxZoom);
1692
+ if (clampedZoom !== zoom.value) {
1693
+ const centerX = (touch1.clientX + touch2.clientX) / 2;
1694
+ const centerY = (touch1.clientY + touch2.clientY) / 2;
1695
+ const rect = containerElement.getBoundingClientRect();
1696
+ const containerCenterX = rect.width / 2;
1697
+ const containerCenterY = rect.height / 2;
1698
+ const offsetX = centerX - rect.left - containerCenterX;
1699
+ const offsetY = centerY - rect.top - containerCenterY;
1700
+ const contentFocalX = (touchZoomState.value.centerX - touchZoomState.value.initialTranslateX) / touchZoomState.value.initialZoom;
1701
+ const contentFocalY = (touchZoomState.value.centerY - touchZoomState.value.initialTranslateY) / touchZoomState.value.initialZoom;
1702
+ zoom.value = clampedZoom;
1703
+ translateX.value = offsetX - contentFocalX * clampedZoom;
1704
+ translateY.value = offsetY - contentFocalY * clampedZoom;
1705
+ }
1706
+ }
1707
+ }
1708
+ function handleTouchEnd(event) {
1709
+ if (event.touches.length < 2) {
1710
+ touchZoomState.value = null;
1711
+ isPinching.value = false;
1712
+ }
1713
+ }
1618
1714
  function getState() {
1619
1715
  return {
1620
1716
  zoom: zoom.value,
@@ -1642,9 +1738,12 @@ function useZoom(options = {}) {
1642
1738
  stopDrag,
1643
1739
  handleWheel,
1644
1740
  getState,
1645
- setState
1741
+ setState,
1742
+ handleTouchStart,
1743
+ handleTouchMove,
1744
+ handleTouchEnd
1646
1745
  };
1647
1746
  }
1648
1747
 
1649
1748
  //#endregion
1650
- export { transformUrl as A, useContext as B, findNodeParent as C, flow as D, hasShiki as E, DEFAULT_LIGHT_THEME as F, LANGUAGE_ALIAS as I, LANGUAGE_EXTENSIONS as L, ICONS as M, DEFAULT_HARDEN_OPTIONS as N, save as O, DEFAULT_DARK_THEME as P, LANGUAGE_ICONS as R, findLastLeafNode as S, hasMermaid as T, escapeMarkdownTableCell as _, useMermaid as a, tableDataToMarkdown as b, _defineProperty as c, SUPPORT_LANGUAGES as d, currentLocale as f, useHardenSanitizers as g, localesGlob as h, useShiki as i, SHADCN_SCHEMAS as j, svgToPngBlob as k, useKatex as l, localeMessages as m, useTippy as n, useMathRenderer as o, loadLocaleMessages as p, useTailwindV3Theme as r, throttle as s, useZoom as t, useI18n as u, extractTableDataFromElement as v, hasKatex as w, tableDataToTSV as x, tableDataToCSV as y, useControls as z };
1749
+ export { transformUrl as A, useContext as B, findNodeParent as C, flow as D, hasShiki as E, DEFAULT_LIGHT_THEME as F, LANGUAGE_ALIAS as I, LANGUAGE_EXTENSIONS as L, ICONS as M, DEFAULT_HARDEN_OPTIONS as N, save as O, DEFAULT_DARK_THEME as P, LANGUAGE_ICONS as R, findLastLeafNode as S, hasMermaid as T, useCodeOptions as V, escapeMarkdownTableCell as _, useMermaid as a, tableDataToMarkdown as b, _defineProperty as c, SUPPORT_LANGUAGES as d, currentLocale as f, useHardenSanitizers as g, localesGlob as h, useShiki as i, SHADCN_SCHEMAS as j, svgToPngBlob as k, useKatex as l, localeMessages as m, useTippy as n, useMathRenderer as o, loadLocaleMessages as p, useTailwindV3Theme as r, throttle as s, useZoom as t, useI18n as u, extractTableDataFromElement as v, hasKatex as w, tableDataToTSV as x, tableDataToCSV as y, useControls as z };
@@ -0,0 +1,5 @@
1
+ import "./composables-Cx0nvyHl.js";
2
+ import "./tooltip-3UDC9mw-.js";
3
+ import { n as dropdown_default } from "./button-jFTF378X.js";
4
+
5
+ export { dropdown_default as default };
@@ -0,0 +1,4 @@
1
+ import "./composables-Cx0nvyHl.js";
2
+ import { t as error_component_default } from "./error-component-Bhl770lu.js";
3
+
4
+ export { error_component_default as default };
@@ -1,4 +1,4 @@
1
- import { M as ICONS, u as useI18n } from "./composables-CZ7YmjNl.js";
1
+ import { B as useContext, u as useI18n } from "./composables-Cx0nvyHl.js";
2
2
  import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, openBlock, renderSlot, resolveDynamicComponent, toDisplayString } from "vue";
3
3
 
4
4
  //#region src/components/error-component.vue?vue&type=script&setup=true&lang.ts
@@ -25,6 +25,7 @@ var error_component_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ *
25
25
  setup(__props) {
26
26
  const props = __props;
27
27
  const { t } = useI18n();
28
+ const { icons } = useContext();
28
29
  const messages = computed(() => ({
29
30
  "vanilla": t("error.vanilla"),
30
31
  "image": t("error.image"),
@@ -35,8 +36,8 @@ var error_component_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ *
35
36
  }));
36
37
  const icon = computed(() => {
37
38
  if (props.icon) return props.icon;
38
- if (ICONS[props.variant]) return ICONS[props.variant];
39
- return ICONS[props.variant.replace("harden-", "")] || ICONS.error;
39
+ if (icons.value[props.variant]) return icons.value[props.variant];
40
+ return icons.value[props.variant.replace("harden-", "")] || icons.value.error;
40
41
  });
41
42
  const message = computed(() => props.message ? props.message : messages.value[props.variant] || messages.value.vanilla);
42
43
  const isHarden = computed(() => {
@@ -1,7 +1,7 @@
1
- import { M as ICONS, O as save, g as useHardenSanitizers, u as useI18n, z as useControls } from "./composables-CZ7YmjNl.js";
2
- import "./tooltip-KcTe6tBF.js";
3
- import { t as button_default } from "./button-vKhxUL3C.js";
4
- import { t as error_component_default } from "./error-component-ChEpOmSX.js";
1
+ import { B as useContext, O as save, g as useHardenSanitizers, u as useI18n, z as useControls } from "./composables-Cx0nvyHl.js";
2
+ import "./tooltip-3UDC9mw-.js";
3
+ import { t as button_default } from "./button-jFTF378X.js";
4
+ import { t as error_component_default } from "./error-component-Bhl770lu.js";
5
5
  import { t as spin_default } from "./spin-Ds5W7qC_.js";
6
6
  import { Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeProps, normalizeStyle, openBlock, ref, resolveDynamicComponent, toDisplayString, toRefs, unref, withCtx } from "vue";
7
7
 
@@ -84,6 +84,7 @@ var image_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
84
84
  const props = __props;
85
85
  const fileExtensionPattern = /\.[^/.]+$/;
86
86
  const { t } = useI18n();
87
+ const { icons } = useContext();
87
88
  const { controls, hardenOptions } = toRefs(props);
88
89
  const { isControlEnabled } = useControls({ controls });
89
90
  const imgRef = ref();
@@ -167,7 +168,7 @@ var image_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineCo
167
168
  }, [!isLoading.value && showDownload.value ? (openBlock(), createBlock(button_default, {
168
169
  key: 0,
169
170
  "data-stream-markdown": "image-download-button",
170
- icon: unref(ICONS).download,
171
+ icon: unref(icons).download,
171
172
  name: unref(t)("button.download"),
172
173
  "icon-class": "test",
173
174
  "icon-width": 16,