@particle-academy/react-fancy 4.8.1 → 4.10.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/README.md CHANGED
@@ -166,6 +166,18 @@ npx vite build # Build demo app (verifies imports work)
166
166
  | Diagram | Entity-relationship diagram with draggable nodes and relation lines | [docs](docs/Diagram.md) |
167
167
  | ContentRenderer | Markdown/HTML content renderer | [docs](docs/ContentRenderer.md) |
168
168
 
169
+ ### Media Viewers
170
+
171
+ Standalone, read-only viewers for displaying media files — drop one in anywhere, or use `<MediaViewer>` to pick the right one from a `mime`/`src` when you don't know the type ahead of time (e.g. showing a file from a tree). SVG renders through `ImageViewer`.
172
+
173
+ | Component | Description | Docs |
174
+ |-----------|-------------|------|
175
+ | MediaViewer | Picks image/video/audio/PDF from `mime` (or `src`) + download fallback | [docs](docs/MediaViewer.md) |
176
+ | ImageViewer | Fit-to-container image with zoom/pan and transparency checkerboard | [docs](docs/ImageViewer.md) |
177
+ | VideoViewer | Native video controls, poster, fit | [docs](docs/VideoViewer.md) |
178
+ | AudioViewer | Themed card around the native audio player | [docs](docs/AudioViewer.md) |
179
+ | PdfViewer | Inline PDF via `<object>`/`<iframe>` with download fallback | [docs](docs/PdfViewer.md) |
180
+
169
181
  ### Human+ Primitives
170
182
 
171
183
  Components for surfaces where humans and AI agents trade control fluidly. Each promoted from the [`dreaming`](https://github.com/Particle-Academy/pa-ux-sandbox/tree/dreaming) sandbox after the API stabilized.
@@ -191,6 +203,7 @@ Components for surfaces where humans and AI agents trade control fluidly. Each p
191
203
  |--------|-------------|------|
192
204
  | Portal | `createPortal` wrapper with automatic dark mode propagation | [docs](docs/Portal.md) |
193
205
  | `cn()` | `clsx` + `tailwind-merge` for conditional class composition | [docs](docs/utilities.md) |
206
+ | `resolveMediaType()` | Resolve `image`/`video`/`audio`/`pdf`/`unknown` from a `mime` and/or `src` | [docs](docs/MediaViewer.md) |
194
207
  | Hooks | useControllableState, useFloatingPosition, useOutsideClick, useEscapeKey, useFocusTrap, useAnimation, useId, usePanZoom | [docs](docs/hooks.md) |
195
208
 
196
209
  ## Customization
package/dist/index.cjs CHANGED
@@ -11656,11 +11656,10 @@ function ContentRenderer({
11656
11656
  );
11657
11657
  }
11658
11658
  ContentRenderer.displayName = "ContentRenderer";
11659
- function toHtml(value, outputFormat, unsafe) {
11659
+ function toHtml(value, outputFormat, unsafe, valueFormat = "auto") {
11660
11660
  if (!value) return "";
11661
11661
  const raw = (() => {
11662
- if (outputFormat === "html") return value;
11663
- const format = detectFormat(value);
11662
+ const format = valueFormat !== "auto" ? valueFormat : outputFormat === "html" ? "html" : detectFormat(value);
11664
11663
  if (format === "html") return value;
11665
11664
  return marked.marked.parse(value, { async: false }).trim();
11666
11665
  })();
@@ -11673,6 +11672,7 @@ function EditorRoot({
11673
11672
  defaultValue = "",
11674
11673
  onChange,
11675
11674
  outputFormat = "html",
11675
+ valueFormat = "auto",
11676
11676
  lineSpacing = 1.6,
11677
11677
  placeholder,
11678
11678
  mode: modeProp,
@@ -11684,13 +11684,13 @@ function EditorRoot({
11684
11684
  const mode = useFieldMode(modeProp);
11685
11685
  const isView = mode === "view";
11686
11686
  const initialHtml = react.useMemo(
11687
- () => toHtml(value, outputFormat, unsafe),
11687
+ () => toHtml(value, outputFormat, unsafe, valueFormat),
11688
11688
  // Seed the contentEditable from the LIVE value when (re)entering edit mode.
11689
11689
  // EditorContent reads this once on mount, and it only mounts in edit mode —
11690
11690
  // so this captures the current value on view→edit, not a stale mount snapshot,
11691
11691
  // and does NOT re-run on every keystroke.
11692
11692
  // eslint-disable-next-line react-hooks/exhaustive-deps
11693
- [isView, outputFormat, unsafe]
11693
+ [isView, outputFormat, unsafe, valueFormat]
11694
11694
  );
11695
11695
  const extensions = react.useMemo(
11696
11696
  () => mergeExtensions(instanceExtensions),
@@ -11767,7 +11767,7 @@ function EditorRoot({
11767
11767
  ContentRenderer,
11768
11768
  {
11769
11769
  value,
11770
- format: outputFormat,
11770
+ format: valueFormat !== "auto" ? valueFormat : outputFormat,
11771
11771
  lineSpacing,
11772
11772
  extensions: instanceExtensions,
11773
11773
  unsafe,
@@ -13026,6 +13026,108 @@ TreeNavRoot.displayName = "TreeNav";
13026
13026
  var TreeNav = Object.assign(TreeNavRoot, {
13027
13027
  Node: TreeNode
13028
13028
  });
13029
+
13030
+ // src/utils/media-type.ts
13031
+ var IMAGE_EXTS = /* @__PURE__ */ new Set([
13032
+ "png",
13033
+ "jpg",
13034
+ "jpeg",
13035
+ "jfif",
13036
+ "pjpeg",
13037
+ "gif",
13038
+ "webp",
13039
+ "avif",
13040
+ "bmp",
13041
+ "ico",
13042
+ "cur",
13043
+ "svg",
13044
+ "svgz",
13045
+ "apng",
13046
+ "tif",
13047
+ "tiff",
13048
+ "heic",
13049
+ "heif"
13050
+ ]);
13051
+ var VIDEO_EXTS = /* @__PURE__ */ new Set([
13052
+ "mp4",
13053
+ "m4v",
13054
+ "webm",
13055
+ "ogv",
13056
+ "ogm",
13057
+ "mov",
13058
+ "mkv",
13059
+ "avi",
13060
+ "wmv",
13061
+ "flv",
13062
+ "3gp",
13063
+ "3g2",
13064
+ "mpg",
13065
+ "mpeg"
13066
+ ]);
13067
+ var AUDIO_EXTS = /* @__PURE__ */ new Set([
13068
+ "mp3",
13069
+ "wav",
13070
+ "wave",
13071
+ "ogg",
13072
+ "oga",
13073
+ "m4a",
13074
+ "m4b",
13075
+ "aac",
13076
+ "flac",
13077
+ "opus",
13078
+ "weba",
13079
+ "mid",
13080
+ "midi",
13081
+ "aiff",
13082
+ "aif"
13083
+ ]);
13084
+ function mimeToKind(mime) {
13085
+ const m = mime.toLowerCase().split(";")[0].trim();
13086
+ if (!m) return "unknown";
13087
+ if (m === "application/pdf" || m === "application/x-pdf") return "pdf";
13088
+ if (m.startsWith("image/")) return "image";
13089
+ if (m.startsWith("video/")) return "video";
13090
+ if (m.startsWith("audio/")) return "audio";
13091
+ if (m === "application/ogg") return "audio";
13092
+ return "unknown";
13093
+ }
13094
+ function extToKind(ext) {
13095
+ const e = ext.toLowerCase();
13096
+ if (e === "pdf") return "pdf";
13097
+ if (IMAGE_EXTS.has(e)) return "image";
13098
+ if (VIDEO_EXTS.has(e)) return "video";
13099
+ if (AUDIO_EXTS.has(e)) return "audio";
13100
+ return "unknown";
13101
+ }
13102
+ function extFromSrc(src) {
13103
+ const clean = src.split(/[?#]/, 1)[0];
13104
+ const lastSeg = clean.split("/").pop() ?? "";
13105
+ const dot = lastSeg.lastIndexOf(".");
13106
+ if (dot <= 0 || dot === lastSeg.length - 1) return null;
13107
+ return lastSeg.slice(dot + 1);
13108
+ }
13109
+ function mimeFromDataUri(src) {
13110
+ if (!/^data:/i.test(src)) return null;
13111
+ const header = src.slice(5).split(",", 1)[0];
13112
+ const mt = header.split(";")[0].trim();
13113
+ return mt || null;
13114
+ }
13115
+ function resolveMediaType({ mime, src }) {
13116
+ if (mime) {
13117
+ const k = mimeToKind(mime);
13118
+ if (k !== "unknown") return k;
13119
+ }
13120
+ if (src) {
13121
+ const dataMime = mimeFromDataUri(src);
13122
+ if (dataMime) {
13123
+ const k = mimeToKind(dataMime);
13124
+ if (k !== "unknown") return k;
13125
+ }
13126
+ const ext = extFromSrc(src);
13127
+ if (ext) return extToKind(ext);
13128
+ }
13129
+ return "unknown";
13130
+ }
13029
13131
  var CONFIDENCE_TIERS = [
13030
13132
  { min: 0.85, color: "#10b981", label: "high" },
13031
13133
  { min: 0.6, color: "#f59e0b", label: "medium" },
@@ -14298,6 +14400,446 @@ function caretRect(ta, start, end) {
14298
14400
  const y = taRect.top - parentRect.top + offsetY - ta.scrollTop;
14299
14401
  return { x, y };
14300
14402
  }
14403
+ var DEFAULT_VIEWPORT = { panX: 0, panY: 0, zoom: 1 };
14404
+ var fitClasses = {
14405
+ contain: "h-full w-full object-contain",
14406
+ cover: "h-full w-full object-cover",
14407
+ none: "max-w-none object-none"
14408
+ };
14409
+ function clamp2(n, min, max) {
14410
+ return Math.min(max, Math.max(min, n));
14411
+ }
14412
+ var ImageViewer = react.forwardRef(
14413
+ ({
14414
+ src,
14415
+ alt = "",
14416
+ fit = "contain",
14417
+ zoomable = true,
14418
+ pannable = true,
14419
+ controls,
14420
+ minZoom = 0.25,
14421
+ maxZoom = 8,
14422
+ checkerboard = true,
14423
+ viewport,
14424
+ defaultViewport,
14425
+ onViewportChange,
14426
+ onLoad,
14427
+ onError,
14428
+ className,
14429
+ style
14430
+ }, ref) => {
14431
+ const containerRef = react.useRef(null);
14432
+ const [status, setStatus] = react.useState("loading");
14433
+ const isControlled = viewport !== void 0;
14434
+ const [internal, setInternal] = react.useState(
14435
+ defaultViewport ?? DEFAULT_VIEWPORT
14436
+ );
14437
+ const current = isControlled ? viewport : internal;
14438
+ const currentRef = react.useRef(current);
14439
+ currentRef.current = current;
14440
+ const setViewport = react.useCallback(
14441
+ (next) => {
14442
+ const resolved = typeof next === "function" ? next(currentRef.current) : next;
14443
+ if (!isControlled) setInternal(resolved);
14444
+ onViewportChange?.(resolved);
14445
+ },
14446
+ [isControlled, onViewportChange]
14447
+ );
14448
+ const interactive = status === "loaded";
14449
+ const { containerProps, isPanning } = usePanZoom({
14450
+ viewport: current,
14451
+ setViewport,
14452
+ minZoom,
14453
+ maxZoom,
14454
+ pannable: pannable && interactive,
14455
+ zoomable: zoomable && interactive,
14456
+ containerRef
14457
+ });
14458
+ const zoomBy = react.useCallback(
14459
+ (factor) => {
14460
+ const el = containerRef.current;
14461
+ if (!el) return;
14462
+ const rect = el.getBoundingClientRect();
14463
+ const cx = rect.width / 2;
14464
+ const cy = rect.height / 2;
14465
+ setViewport((prev) => {
14466
+ const newZoom = clamp2(prev.zoom * factor, minZoom, maxZoom);
14467
+ const ratio = newZoom / prev.zoom;
14468
+ return {
14469
+ zoom: newZoom,
14470
+ panX: cx - (cx - prev.panX) * ratio,
14471
+ panY: cy - (cy - prev.panY) * ratio
14472
+ };
14473
+ });
14474
+ },
14475
+ [setViewport, minZoom, maxZoom]
14476
+ );
14477
+ const reset = react.useCallback(() => setViewport(DEFAULT_VIEWPORT), [setViewport]);
14478
+ const showControls = (controls ?? zoomable) && interactive;
14479
+ const transform = `translate(${current.panX}px, ${current.panY}px) scale(${current.zoom})`;
14480
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14481
+ "div",
14482
+ {
14483
+ ref,
14484
+ "data-react-fancy-image-viewer": "",
14485
+ className: cn(
14486
+ "relative isolate overflow-hidden rounded-md select-none",
14487
+ checkerboard ? "fancy-checkerboard" : "bg-zinc-100 dark:bg-zinc-900",
14488
+ className
14489
+ ),
14490
+ style,
14491
+ children: [
14492
+ /* @__PURE__ */ jsxRuntime.jsx(
14493
+ "div",
14494
+ {
14495
+ ref: containerRef,
14496
+ "data-canvas-bg": "",
14497
+ className: cn(
14498
+ "absolute inset-0 flex items-center justify-center",
14499
+ isPanning ? "cursor-grabbing" : pannable && interactive ? "cursor-grab" : ""
14500
+ ),
14501
+ style: {
14502
+ transform,
14503
+ transformOrigin: "0 0",
14504
+ willChange: "transform",
14505
+ touchAction: zoomable || pannable ? "none" : void 0
14506
+ },
14507
+ onDoubleClick: zoomable ? reset : void 0,
14508
+ ...containerProps,
14509
+ children: /* @__PURE__ */ jsxRuntime.jsx(
14510
+ "img",
14511
+ {
14512
+ src,
14513
+ alt,
14514
+ draggable: false,
14515
+ onLoad: () => {
14516
+ setStatus("loaded");
14517
+ onLoad?.();
14518
+ },
14519
+ onError: () => {
14520
+ setStatus("error");
14521
+ onError?.();
14522
+ },
14523
+ className: cn(
14524
+ "pointer-events-none block",
14525
+ status === "error" && "hidden",
14526
+ fitClasses[fit]
14527
+ )
14528
+ }
14529
+ )
14530
+ }
14531
+ ),
14532
+ status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(
14533
+ "div",
14534
+ {
14535
+ "data-react-fancy-image-viewer-error": "",
14536
+ className: "absolute inset-0 flex flex-col items-center justify-center gap-1 p-4 text-center text-sm text-zinc-500 dark:text-zinc-400",
14537
+ children: [
14538
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Couldn't load image" }),
14539
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-full truncate text-xs opacity-70", children: alt || src })
14540
+ ]
14541
+ }
14542
+ ),
14543
+ showControls && /* @__PURE__ */ jsxRuntime.jsxs(
14544
+ "div",
14545
+ {
14546
+ "data-react-fancy-image-viewer-controls": "",
14547
+ className: "absolute bottom-2 right-2 flex items-center gap-0.5 rounded-md border border-zinc-200 bg-white/90 p-0.5 text-zinc-700 shadow-sm backdrop-blur dark:border-zinc-700 dark:bg-zinc-900/90 dark:text-zinc-200",
14548
+ children: [
14549
+ /* @__PURE__ */ jsxRuntime.jsx(
14550
+ "button",
14551
+ {
14552
+ type: "button",
14553
+ "aria-label": "Zoom out",
14554
+ onClick: () => zoomBy(1 / 1.25),
14555
+ className: "flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 dark:hover:bg-zinc-800",
14556
+ children: "\u2212"
14557
+ }
14558
+ ),
14559
+ /* @__PURE__ */ jsxRuntime.jsxs(
14560
+ "button",
14561
+ {
14562
+ type: "button",
14563
+ "aria-label": "Reset zoom",
14564
+ onClick: reset,
14565
+ className: "min-w-[3rem] rounded px-1 text-center text-xs tabular-nums hover:bg-zinc-100 dark:hover:bg-zinc-800",
14566
+ children: [
14567
+ Math.round(current.zoom * 100),
14568
+ "%"
14569
+ ]
14570
+ }
14571
+ ),
14572
+ /* @__PURE__ */ jsxRuntime.jsx(
14573
+ "button",
14574
+ {
14575
+ type: "button",
14576
+ "aria-label": "Zoom in",
14577
+ onClick: () => zoomBy(1.25),
14578
+ className: "flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 dark:hover:bg-zinc-800",
14579
+ children: "+"
14580
+ }
14581
+ )
14582
+ ]
14583
+ }
14584
+ )
14585
+ ]
14586
+ }
14587
+ );
14588
+ }
14589
+ );
14590
+ ImageViewer.displayName = "ImageViewer";
14591
+ var VideoViewer = react.forwardRef(
14592
+ ({
14593
+ src,
14594
+ poster,
14595
+ controls = true,
14596
+ autoPlay = false,
14597
+ loop = false,
14598
+ muted = false,
14599
+ fit = "contain",
14600
+ onError,
14601
+ className,
14602
+ style
14603
+ }, ref) => {
14604
+ const [errored, setErrored] = react.useState(false);
14605
+ return /* @__PURE__ */ jsxRuntime.jsx(
14606
+ "div",
14607
+ {
14608
+ ref,
14609
+ "data-react-fancy-video-viewer": "",
14610
+ className: cn(
14611
+ "relative flex items-center justify-center overflow-hidden rounded-md bg-black",
14612
+ className
14613
+ ),
14614
+ style,
14615
+ children: errored ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center gap-1 p-4 text-center text-sm text-zinc-400", children: [
14616
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: "Couldn't load video" }),
14617
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-full truncate text-xs opacity-70", children: src })
14618
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
14619
+ "video",
14620
+ {
14621
+ src,
14622
+ poster,
14623
+ controls,
14624
+ autoPlay,
14625
+ loop,
14626
+ muted,
14627
+ playsInline: true,
14628
+ onError: () => {
14629
+ setErrored(true);
14630
+ onError?.();
14631
+ },
14632
+ className: cn(
14633
+ "max-h-full max-w-full",
14634
+ fit === "cover" ? "h-full w-full object-cover" : "object-contain"
14635
+ )
14636
+ }
14637
+ )
14638
+ }
14639
+ );
14640
+ }
14641
+ );
14642
+ VideoViewer.displayName = "VideoViewer";
14643
+ var AudioViewer = react.forwardRef(
14644
+ ({
14645
+ src,
14646
+ title,
14647
+ controls = true,
14648
+ autoPlay = false,
14649
+ loop = false,
14650
+ onError,
14651
+ className,
14652
+ style
14653
+ }, ref) => {
14654
+ const [errored, setErrored] = react.useState(false);
14655
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14656
+ "div",
14657
+ {
14658
+ ref,
14659
+ "data-react-fancy-audio-viewer": "",
14660
+ className: cn(
14661
+ "flex flex-col gap-2 rounded-lg border border-zinc-200 bg-white p-4 dark:border-zinc-700 dark:bg-zinc-900",
14662
+ className
14663
+ ),
14664
+ style,
14665
+ children: [
14666
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-zinc-700 dark:text-zinc-200", children: title }),
14667
+ errored ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Couldn't load audio." }) : /* @__PURE__ */ jsxRuntime.jsx(
14668
+ "audio",
14669
+ {
14670
+ src,
14671
+ controls,
14672
+ autoPlay,
14673
+ loop,
14674
+ onError: () => {
14675
+ setErrored(true);
14676
+ onError?.();
14677
+ },
14678
+ className: "w-full"
14679
+ }
14680
+ )
14681
+ ]
14682
+ }
14683
+ );
14684
+ }
14685
+ );
14686
+ AudioViewer.displayName = "AudioViewer";
14687
+ var PdfViewer = react.forwardRef(
14688
+ ({ src, title = "PDF document", className, style }, ref) => {
14689
+ return /* @__PURE__ */ jsxRuntime.jsx(
14690
+ "div",
14691
+ {
14692
+ ref,
14693
+ "data-react-fancy-pdf-viewer": "",
14694
+ className: cn(
14695
+ "relative h-full min-h-[20rem] w-full overflow-hidden rounded-md bg-zinc-100 dark:bg-zinc-900",
14696
+ className
14697
+ ),
14698
+ style,
14699
+ children: /* @__PURE__ */ jsxRuntime.jsxs("object", { data: src, type: "application/pdf", className: "h-full w-full", children: [
14700
+ /* @__PURE__ */ jsxRuntime.jsx(
14701
+ "iframe",
14702
+ {
14703
+ src,
14704
+ title,
14705
+ className: "h-full w-full border-0"
14706
+ }
14707
+ ),
14708
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-2 p-4 text-center text-sm text-zinc-500 dark:text-zinc-400", children: [
14709
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "This browser can't display the PDF inline." }),
14710
+ /* @__PURE__ */ jsxRuntime.jsx(
14711
+ "a",
14712
+ {
14713
+ href: src,
14714
+ download: true,
14715
+ className: "font-medium text-blue-600 hover:underline dark:text-blue-400",
14716
+ children: "Download PDF"
14717
+ }
14718
+ )
14719
+ ] })
14720
+ ] })
14721
+ }
14722
+ );
14723
+ }
14724
+ );
14725
+ PdfViewer.displayName = "PdfViewer";
14726
+ function basename(src) {
14727
+ if (/^(data|blob):/i.test(src)) return null;
14728
+ const seg = src.split(/[?#]/, 1)[0].split("/").pop();
14729
+ return seg ? decodeURIComponent(seg) : null;
14730
+ }
14731
+ function MediaFallback({ src, mime, label, className }) {
14732
+ const name = label || basename(src);
14733
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14734
+ "div",
14735
+ {
14736
+ "data-react-fancy-media-fallback": "",
14737
+ className: cn(
14738
+ "flex flex-col items-center justify-center gap-2 rounded-lg border border-zinc-200 bg-white p-6 text-center dark:border-zinc-700 dark:bg-zinc-900",
14739
+ className
14740
+ ),
14741
+ children: [
14742
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-200", children: "No preview available" }),
14743
+ (name || mime) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "max-w-full truncate text-xs text-zinc-500 dark:text-zinc-400", children: [
14744
+ name,
14745
+ name && mime ? " \xB7 " : "",
14746
+ mime
14747
+ ] }),
14748
+ /* @__PURE__ */ jsxRuntime.jsx(
14749
+ "a",
14750
+ {
14751
+ href: src,
14752
+ download: name ?? void 0,
14753
+ className: "text-xs font-medium text-blue-600 hover:underline dark:text-blue-400",
14754
+ children: "Download"
14755
+ }
14756
+ )
14757
+ ]
14758
+ }
14759
+ );
14760
+ }
14761
+ var MediaViewer = react.forwardRef(
14762
+ ({
14763
+ src,
14764
+ mime,
14765
+ alt,
14766
+ kind,
14767
+ imageProps,
14768
+ videoProps,
14769
+ audioProps,
14770
+ pdfProps,
14771
+ fallback,
14772
+ onError,
14773
+ className,
14774
+ style
14775
+ }, ref) => {
14776
+ const resolved = kind ?? resolveMediaType({ mime, src });
14777
+ switch (resolved) {
14778
+ case "image":
14779
+ return /* @__PURE__ */ jsxRuntime.jsx(
14780
+ ImageViewer,
14781
+ {
14782
+ ref,
14783
+ src,
14784
+ alt: alt ?? "",
14785
+ onError,
14786
+ className,
14787
+ style,
14788
+ ...imageProps
14789
+ }
14790
+ );
14791
+ case "video":
14792
+ return /* @__PURE__ */ jsxRuntime.jsx(
14793
+ VideoViewer,
14794
+ {
14795
+ ref,
14796
+ src,
14797
+ onError,
14798
+ className,
14799
+ style,
14800
+ ...videoProps
14801
+ }
14802
+ );
14803
+ case "audio":
14804
+ return /* @__PURE__ */ jsxRuntime.jsx(
14805
+ AudioViewer,
14806
+ {
14807
+ ref,
14808
+ src,
14809
+ title: alt,
14810
+ onError,
14811
+ className,
14812
+ style,
14813
+ ...audioProps
14814
+ }
14815
+ );
14816
+ case "pdf":
14817
+ return /* @__PURE__ */ jsxRuntime.jsx(
14818
+ PdfViewer,
14819
+ {
14820
+ ref,
14821
+ src,
14822
+ title: alt,
14823
+ className,
14824
+ style,
14825
+ ...pdfProps
14826
+ }
14827
+ );
14828
+ default:
14829
+ return /* @__PURE__ */ jsxRuntime.jsx(
14830
+ "div",
14831
+ {
14832
+ ref,
14833
+ "data-react-fancy-media-viewer": "",
14834
+ className,
14835
+ style,
14836
+ children: fallback ?? /* @__PURE__ */ jsxRuntime.jsx(MediaFallback, { src, mime, label: alt })
14837
+ }
14838
+ );
14839
+ }
14840
+ }
14841
+ );
14842
+ MediaViewer.displayName = "MediaViewer";
14301
14843
 
14302
14844
  exports.Accordion = Accordion;
14303
14845
  exports.AccordionPanel = AccordionPanel;
@@ -14305,6 +14847,7 @@ exports.AccordionPanelContent = AccordionPanelContent;
14305
14847
  exports.AccordionPanelSection = AccordionPanelSection;
14306
14848
  exports.AccordionPanelTrigger = AccordionPanelTrigger;
14307
14849
  exports.Action = Action;
14850
+ exports.AudioViewer = AudioViewer;
14308
14851
  exports.Autocomplete = Autocomplete;
14309
14852
  exports.Avatar = Avatar;
14310
14853
  exports.Badge = Badge;
@@ -14341,10 +14884,12 @@ exports.Form = Form;
14341
14884
  exports.FormProvider = FormProvider;
14342
14885
  exports.Heading = Heading;
14343
14886
  exports.Icon = Icon;
14887
+ exports.ImageViewer = ImageViewer;
14344
14888
  exports.Input = Input;
14345
14889
  exports.InputTag = InputTag;
14346
14890
  exports.Kanban = Kanban;
14347
14891
  exports.MagicWand = MagicWand;
14892
+ exports.MediaViewer = MediaViewer;
14348
14893
  exports.Menu = Menu2;
14349
14894
  exports.MobileMenu = MobileMenu;
14350
14895
  exports.Modal = Modal;
@@ -14353,6 +14898,7 @@ exports.MultiSwitch = MultiSwitch;
14353
14898
  exports.Navbar = Navbar;
14354
14899
  exports.OtpInput = OtpInput;
14355
14900
  exports.Pagination = Pagination;
14901
+ exports.PdfViewer = PdfViewer;
14356
14902
  exports.Pillbox = Pillbox;
14357
14903
  exports.Popover = Popover;
14358
14904
  exports.Portal = Portal;
@@ -14379,6 +14925,7 @@ exports.Timeline = Timeline;
14379
14925
  exports.Toast = Toast;
14380
14926
  exports.Tooltip = Tooltip;
14381
14927
  exports.TreeNav = TreeNav;
14928
+ exports.VideoViewer = VideoViewer;
14382
14929
  exports.applyTone = applyTone;
14383
14930
  exports.cn = cn;
14384
14931
  exports.configureIcons = configureIcons;
@@ -14393,6 +14940,7 @@ exports.registerIconAddendum = registerIconAddendum;
14393
14940
  exports.registerIconSet = registerIconSet;
14394
14941
  exports.registerIcons = registerIcons;
14395
14942
  exports.resolve = resolve;
14943
+ exports.resolveMediaType = resolveMediaType;
14396
14944
  exports.sanitizeHref = sanitizeHref;
14397
14945
  exports.sanitizeHtml = sanitizeHtml;
14398
14946
  exports.search = search;