@svadmin/ui 0.55.0 → 0.55.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.
@@ -25,6 +25,7 @@ import CodeField from './fields/CodeField.svelte';
25
25
  import AvatarField from './fields/AvatarField.svelte';
26
26
  import CopyField from './fields/CopyField.svelte';
27
27
  import FileField from './fields/FileField.svelte';
28
+ import MarkdownField from './fields/MarkdownField.svelte';
28
29
  /**
29
30
  * Built-in display components for show/detail pages.
30
31
  * Keys match FieldDefinition.type values.
@@ -55,6 +56,7 @@ export const builtinDisplayComponents = {
55
56
  avatar: AvatarField,
56
57
  copy: CopyField,
57
58
  file: FileField,
59
+ markdown: MarkdownField,
58
60
  };
59
61
  // ─── Custom component registry ─────────────────────────────────
60
62
  // Users can register custom field display components at runtime
@@ -1,17 +1,58 @@
1
1
  <script lang="ts">
2
2
  /* eslint-disable svelte/no-at-html-tags */
3
- import * as DOMPurifyPkg from "isomorphic-dompurify";
4
- interface Props { value: string | null | undefined }
5
- let { value }: Props = $props();
3
+ import { onMount } from "svelte";
6
4
 
7
- const DOMPurify = 'default' in DOMPurifyPkg ? DOMPurifyPkg.default : DOMPurifyPkg;
5
+ interface Props {
6
+ value?: string | null | undefined;
7
+ class?: string;
8
+ }
8
9
 
9
- const sanitizedHtml = $derived(
10
- value ? DOMPurify.sanitize(value, { USE_PROFILES: { html: true } }) : ''
11
- );
10
+ let { value, class: className = "" }: Props = $props();
11
+
12
+ interface DomPurifyLike {
13
+ sanitize(source: string | Node, config?: Record<string, unknown>): string;
14
+ }
15
+ let DOMPurify: DomPurifyLike | null = $state(null);
16
+
17
+ onMount(() => {
18
+ let cancelled = false;
19
+ import("isomorphic-dompurify")
20
+ .then((pkg) => {
21
+ if (!cancelled) {
22
+ const mod = pkg as unknown as { default?: DomPurifyLike; sanitize?: DomPurifyLike["sanitize"] };
23
+ const loaded: DomPurifyLike | undefined =
24
+ mod.default && typeof mod.default.sanitize === "function"
25
+ ? mod.default
26
+ : typeof mod.sanitize === "function"
27
+ ? (mod as DomPurifyLike)
28
+ : undefined;
29
+ if (loaded) {
30
+ DOMPurify = loaded;
31
+ }
32
+ }
33
+ })
34
+ .catch(() => {});
35
+ return () => {
36
+ cancelled = true;
37
+ };
38
+ });
39
+
40
+ const sanitizedHtml = $derived.by(() => {
41
+ if (!value) return "";
42
+ if (DOMPurify && typeof DOMPurify.sanitize === "function") {
43
+ return DOMPurify.sanitize(value, { USE_PROFILES: { html: true } });
44
+ }
45
+ return String(value)
46
+ .replace(/&/g, "&amp;")
47
+ .replace(/</g, "&lt;")
48
+ .replace(/>/g, "&gt;")
49
+ .replace(/"/g, "&quot;")
50
+ .replace(/'/g, "&#039;");
51
+ });
12
52
  </script>
13
- {#if value}
14
- <div class="prose prose-sm dark:prose-invert max-w-none leading-relaxed [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-[0.875em] [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:rounded-lg [&_pre]:overflow-x-auto [&_a]:text-primary">{@html sanitizedHtml}</div>
15
- {:else}
53
+
54
+ {#if !value}
16
55
  <span class="text-muted-foreground">—</span>
56
+ {:else}
57
+ <div class="prose prose-sm dark:prose-invert max-w-none leading-relaxed [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-[0.875em] [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:rounded-lg [&_pre]:overflow-x-auto [&_a]:text-primary {className}">{@html sanitizedHtml}</div>
17
58
  {/if}
@@ -1,5 +1,6 @@
1
1
  interface Props {
2
- value: string | null | undefined;
2
+ value?: string | null | undefined;
3
+ class?: string;
3
4
  }
4
5
  declare const MarkdownField: import("svelte").Component<Props, {}, "">;
5
6
  type MarkdownField = ReturnType<typeof MarkdownField>;
@@ -31,10 +31,16 @@
31
31
  class: className = '',
32
32
  }: Props = $props();
33
33
 
34
+ const normalizedPrecision = $derived(
35
+ precision != null && Number.isFinite(precision)
36
+ ? Math.min(20, Math.max(0, Math.trunc(precision)))
37
+ : undefined
38
+ );
39
+
34
40
  const formatted = $derived.by(() => {
35
41
  if (value == null || value === '') return nullLabel;
36
42
  const num = Number(value);
37
- if (isNaN(num)) return nullLabel;
43
+ if (!Number.isFinite(num)) return nullLabel;
38
44
 
39
45
  const opts: Intl.NumberFormatOptions = { ...options };
40
46
 
@@ -46,9 +52,9 @@
46
52
  opts.notation = 'compact';
47
53
  opts.compactDisplay = 'short';
48
54
  }
49
- if (precision != null) {
50
- opts.minimumFractionDigits = precision;
51
- opts.maximumFractionDigits = precision;
55
+ if (normalizedPrecision !== undefined) {
56
+ opts.minimumFractionDigits = normalizedPrecision;
57
+ opts.maximumFractionDigits = normalizedPrecision;
52
58
  }
53
59
  if (signed && num > 0 && !currency) {
54
60
  opts.signDisplay = 'always';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.55.0",
3
+ "version": "0.55.1",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [