@svadmin/ui 0.40.0 → 0.40.2

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);
@@ -7,7 +7,7 @@
7
7
  base: "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 active:translate-y-px aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
8
8
  variants: {
9
9
  variant: {
10
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
10
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
11
11
  outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground",
12
12
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
13
13
  ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
@@ -3,7 +3,7 @@ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements
3
3
  import { type VariantProps } from "tailwind-variants";
4
4
  export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
5
5
  variant: {
6
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80";
6
+ default: "bg-primary text-primary-foreground hover:bg-primary/80";
7
7
  outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground";
8
8
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground";
9
9
  ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground";
@@ -22,7 +22,7 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
22
22
  };
23
23
  }, undefined, "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-lg border border-transparent bg-clip-padding text-sm font-medium focus-visible:ring-3 active:translate-y-px aria-invalid:ring-3 [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", {
24
24
  variant: {
25
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80";
25
+ default: "bg-primary text-primary-foreground hover:bg-primary/80";
26
26
  outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground";
27
27
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground";
28
28
  ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground";
@@ -41,7 +41,7 @@ export declare const buttonVariants: import("tailwind-variants").TVReturnType<{
41
41
  };
42
42
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
43
43
  variant: {
44
- default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80";
44
+ default: "bg-primary text-primary-foreground hover:bg-primary/80";
45
45
  outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground";
46
46
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground";
47
47
  ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.40.0",
3
+ "version": "0.40.2",
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",