@svadmin/ui 0.40.0 → 0.40.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.
@@ -1,19 +1,6 @@
1
1
  <script lang="ts">
2
2
  /* eslint-disable svelte/no-at-html-tags */
3
- import * as markedPkg from "marked";
4
- import * as markedHighlightPkg from "marked-highlight";
5
- import * as hljsPkg from "highlight.js";
6
- import * as DOMPurifyPkg from "isomorphic-dompurify";
7
-
8
- const Marked = markedPkg.Marked;
9
- const markedHighlight = markedHighlightPkg.markedHighlight;
10
- const hljsModule = hljsPkg.default || hljsPkg;
11
- const DOMPurify = 'default' in DOMPurifyPkg ? DOMPurifyPkg.default : DOMPurifyPkg;
12
-
13
- // Only safely import css if hljs exists
14
- if (hljsModule && Object.keys(hljsModule).length > 0) {
15
- import("highlight.js/styles/github-dark.css").catch(() => {});
16
- }
3
+ import { onMount } from "svelte";
17
4
 
18
5
  interface Props {
19
6
  /** The raw markdown string to render */
@@ -26,25 +13,81 @@
26
13
 
27
14
  let { content, streaming = false, class: className = "" }: Props = $props();
28
15
 
29
- const hasMarkdownDeps = typeof Marked === "function" && typeof DOMPurify?.sanitize === "function";
16
+ type MarkedConstructor = typeof import("marked").Marked;
17
+ type MarkedHighlight = typeof import("marked-highlight").markedHighlight;
18
+ type HighlightApi = typeof import("highlight.js").default;
19
+ type Sanitizer = typeof import("isomorphic-dompurify").default;
20
+
21
+ // Lazily loaded optional peer dependencies (marked, marked-highlight,
22
+ // highlight.js, isomorphic-dompurify). They are declared as optional peer
23
+ // deps; statically importing them would crash consumers that have not
24
+ // installed them, even when MarkdownRenderer is never rendered. Load them
25
+ // dynamically so the module graph resolves without them and the component
26
+ // degrades to escaped-text rendering when they are absent.
27
+ let MarkedCtor = $state<MarkedConstructor | null>(null);
28
+ let markedHighlightFn = $state<MarkedHighlight | null>(null);
29
+ let hljs = $state<HighlightApi | null>(null);
30
+ let DOMPurify = $state<Sanitizer | null>(null);
31
+
32
+ onMount(() => {
33
+ let cancelled = false;
34
+ (async () => {
35
+ try {
36
+ const [markedPkg, markedHighlightPkg, hljsPkg, DOMPurifyPkg] = await Promise.all([
37
+ import("marked"),
38
+ import("marked-highlight"),
39
+ import("highlight.js"),
40
+ import("isomorphic-dompurify"),
41
+ ]);
42
+ if (cancelled) return;
43
+ MarkedCtor = markedPkg.Marked;
44
+ markedHighlightFn = markedHighlightPkg.markedHighlight;
45
+ hljs = hljsPkg.default;
46
+ DOMPurify = DOMPurifyPkg.default;
47
+ // Only import the theme css when highlight.js is available
48
+ if (hljs && Object.keys(hljs).length > 0) {
49
+ import("highlight.js/styles/github-dark.css").catch(() => {});
50
+ }
51
+ } catch {
52
+ // Optional deps not installed; fall back to escaped-text rendering.
53
+ if (cancelled) return;
54
+ }
55
+ })();
56
+ return () => { cancelled = true; };
57
+ });
58
+
59
+ const hasMarkdownDeps = $derived(
60
+ typeof MarkedCtor === "function" &&
61
+ typeof DOMPurify?.sanitize === "function" &&
62
+ typeof markedHighlightFn === "function",
63
+ );
30
64
 
31
65
  // Configure marked with syntax highlighting if available
32
- const markedObj = hasMarkdownDeps ? new Marked(
33
- markedHighlight({
34
- langPrefix: "hljs language-",
35
- highlight(code: string, lang: string) {
36
- const language = hljsModule.getLanguage && hljsModule.getLanguage(lang) ? lang : "plaintext";
37
- return hljsModule.highlight ? hljsModule.highlight(code, { language }).value : code;
38
- },
39
- }),
40
- ) : null;
66
+ const markedObj = $derived.by(() => {
67
+ const Constructor = MarkedCtor;
68
+ const highlightExtension = markedHighlightFn;
69
+ if (!Constructor || !highlightExtension) return null;
70
+
71
+ return new Constructor(
72
+ highlightExtension({
73
+ langPrefix: "hljs language-",
74
+ highlight(code: string, lang: string) {
75
+ const language = hljs?.getLanguage && hljs.getLanguage(lang) ? lang : "plaintext";
76
+ return hljs?.highlight ? hljs.highlight(code, { language }).value : code;
77
+ },
78
+ }),
79
+ );
80
+ });
41
81
 
42
82
  // Render HTML safely
43
- const html = $derived(
44
- hasMarkdownDeps
45
- ? DOMPurify.sanitize(markedObj?.parse(content || "") as string)
46
- : `<div style="white-space: pre-wrap">${String(content || "").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</div>`
47
- );
83
+ const html = $derived.by(() => {
84
+ const purifier = DOMPurify;
85
+ const parser = markedObj;
86
+ if (hasMarkdownDeps && purifier && parser) {
87
+ return purifier.sanitize(parser.parse(content || ""));
88
+ }
89
+ return `<div style="white-space: pre-wrap">${String(content || "").replace(/</g, "&lt;").replace(/>/g, "&gt;")}</div>`;
90
+ });
48
91
 
49
92
  // Handle copy code blocks
50
93
  let copiedBlock = $state<string | null>(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.40.0",
3
+ "version": "0.40.1",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -39,7 +39,7 @@
39
39
  "./components/*": "./dist/components/*"
40
40
  },
41
41
  "peerDependencies": {
42
- "@svadmin/core": "^0.34.0",
42
+ "@svadmin/core": "^0.34.1",
43
43
  "@tanstack/svelte-query": "^6.1.38",
44
44
  "highlight.js": "^11.11.1",
45
45
  "isomorphic-dompurify": "^3.20.0",