@djangocfg/ui-tools 2.1.91

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 (174) hide show
  1. package/dist/LottiePlayer.client-LBEC2JKY.mjs +161 -0
  2. package/dist/LottiePlayer.client-LBEC2JKY.mjs.map +1 -0
  3. package/dist/LottiePlayer.client-WFMG2OOW.cjs +168 -0
  4. package/dist/LottiePlayer.client-WFMG2OOW.cjs.map +1 -0
  5. package/dist/Mermaid.client-4TU2TSH3.mjs +477 -0
  6. package/dist/Mermaid.client-4TU2TSH3.mjs.map +1 -0
  7. package/dist/Mermaid.client-SBYY364Q.cjs +483 -0
  8. package/dist/Mermaid.client-SBYY364Q.cjs.map +1 -0
  9. package/dist/PlaygroundLayout-3YVSAEAF.cjs +1003 -0
  10. package/dist/PlaygroundLayout-3YVSAEAF.cjs.map +1 -0
  11. package/dist/PlaygroundLayout-4DYBORAS.mjs +996 -0
  12. package/dist/PlaygroundLayout-4DYBORAS.mjs.map +1 -0
  13. package/dist/PrettyCode.client-LCBPPTIX.mjs +152 -0
  14. package/dist/PrettyCode.client-LCBPPTIX.mjs.map +1 -0
  15. package/dist/PrettyCode.client-PNPLXRH6.cjs +154 -0
  16. package/dist/PrettyCode.client-PNPLXRH6.cjs.map +1 -0
  17. package/dist/chunk-37ZI6VD4.mjs +12 -0
  18. package/dist/chunk-37ZI6VD4.mjs.map +1 -0
  19. package/dist/chunk-3HK2OE62.cjs +81 -0
  20. package/dist/chunk-3HK2OE62.cjs.map +1 -0
  21. package/dist/chunk-7DGDQVQW.cjs +591 -0
  22. package/dist/chunk-7DGDQVQW.cjs.map +1 -0
  23. package/dist/chunk-M6P2FU7L.mjs +572 -0
  24. package/dist/chunk-M6P2FU7L.mjs.map +1 -0
  25. package/dist/chunk-UQ3XI5MY.cjs +15 -0
  26. package/dist/chunk-UQ3XI5MY.cjs.map +1 -0
  27. package/dist/chunk-YFRNE2IR.mjs +79 -0
  28. package/dist/chunk-YFRNE2IR.mjs.map +1 -0
  29. package/dist/index.cjs +5042 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +1591 -0
  32. package/dist/index.d.ts +1591 -0
  33. package/dist/index.mjs +4941 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/package.json +86 -0
  36. package/src/components/markdown/MarkdownMessage.tsx +340 -0
  37. package/src/components/markdown/index.ts +5 -0
  38. package/src/index.ts +26 -0
  39. package/src/stores/index.ts +9 -0
  40. package/src/stores/mediaCache.ts +534 -0
  41. package/src/tools/AudioPlayer/README.md +206 -0
  42. package/src/tools/AudioPlayer/components/HybridAudioPlayer.tsx +216 -0
  43. package/src/tools/AudioPlayer/components/HybridSimplePlayer.tsx +280 -0
  44. package/src/tools/AudioPlayer/components/HybridWaveform.tsx +279 -0
  45. package/src/tools/AudioPlayer/components/ReactiveCover/AudioReactiveCover.tsx +149 -0
  46. package/src/tools/AudioPlayer/components/ReactiveCover/effects/GlowEffect.tsx +110 -0
  47. package/src/tools/AudioPlayer/components/ReactiveCover/effects/MeshEffect.tsx +58 -0
  48. package/src/tools/AudioPlayer/components/ReactiveCover/effects/OrbsEffect.tsx +45 -0
  49. package/src/tools/AudioPlayer/components/ReactiveCover/effects/SpotlightEffect.tsx +82 -0
  50. package/src/tools/AudioPlayer/components/ReactiveCover/effects/index.ts +8 -0
  51. package/src/tools/AudioPlayer/components/ReactiveCover/index.ts +6 -0
  52. package/src/tools/AudioPlayer/components/index.ts +22 -0
  53. package/src/tools/AudioPlayer/context/HybridAudioProvider.tsx +158 -0
  54. package/src/tools/AudioPlayer/context/index.ts +16 -0
  55. package/src/tools/AudioPlayer/effects/index.ts +412 -0
  56. package/src/tools/AudioPlayer/hooks/index.ts +35 -0
  57. package/src/tools/AudioPlayer/hooks/useHybridAudio.ts +387 -0
  58. package/src/tools/AudioPlayer/hooks/useHybridAudioAnalysis.ts +95 -0
  59. package/src/tools/AudioPlayer/hooks/useVisualization.tsx +207 -0
  60. package/src/tools/AudioPlayer/index.ts +133 -0
  61. package/src/tools/AudioPlayer/types/effects.ts +73 -0
  62. package/src/tools/AudioPlayer/types/index.ts +27 -0
  63. package/src/tools/AudioPlayer/utils/debug.ts +14 -0
  64. package/src/tools/AudioPlayer/utils/formatTime.ts +10 -0
  65. package/src/tools/AudioPlayer/utils/index.ts +6 -0
  66. package/src/tools/ImageViewer/@refactoring/00-PLAN.md +71 -0
  67. package/src/tools/ImageViewer/@refactoring/01-TYPES.md +121 -0
  68. package/src/tools/ImageViewer/@refactoring/02-UTILS.md +143 -0
  69. package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +261 -0
  70. package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +427 -0
  71. package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +126 -0
  72. package/src/tools/ImageViewer/README.md +200 -0
  73. package/src/tools/ImageViewer/components/ImageInfo.tsx +44 -0
  74. package/src/tools/ImageViewer/components/ImageToolbar.tsx +145 -0
  75. package/src/tools/ImageViewer/components/ImageViewer.tsx +241 -0
  76. package/src/tools/ImageViewer/components/index.ts +7 -0
  77. package/src/tools/ImageViewer/hooks/index.ts +9 -0
  78. package/src/tools/ImageViewer/hooks/useImageLoading.ts +204 -0
  79. package/src/tools/ImageViewer/hooks/useImageTransform.ts +101 -0
  80. package/src/tools/ImageViewer/index.ts +60 -0
  81. package/src/tools/ImageViewer/types.ts +81 -0
  82. package/src/tools/ImageViewer/utils/constants.ts +59 -0
  83. package/src/tools/ImageViewer/utils/debug.ts +14 -0
  84. package/src/tools/ImageViewer/utils/index.ts +17 -0
  85. package/src/tools/ImageViewer/utils/lqip.ts +47 -0
  86. package/src/tools/JsonForm/JsonSchemaForm.tsx +197 -0
  87. package/src/tools/JsonForm/examples/BotConfigExample.tsx +249 -0
  88. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +161 -0
  89. package/src/tools/JsonForm/index.ts +46 -0
  90. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +47 -0
  91. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +74 -0
  92. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +107 -0
  93. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +35 -0
  94. package/src/tools/JsonForm/templates/FieldTemplate.tsx +62 -0
  95. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +116 -0
  96. package/src/tools/JsonForm/templates/index.ts +12 -0
  97. package/src/tools/JsonForm/types.ts +83 -0
  98. package/src/tools/JsonForm/utils.ts +213 -0
  99. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +37 -0
  100. package/src/tools/JsonForm/widgets/ColorWidget.tsx +219 -0
  101. package/src/tools/JsonForm/widgets/NumberWidget.tsx +89 -0
  102. package/src/tools/JsonForm/widgets/SelectWidget.tsx +97 -0
  103. package/src/tools/JsonForm/widgets/SliderWidget.tsx +148 -0
  104. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +35 -0
  105. package/src/tools/JsonForm/widgets/TextWidget.tsx +96 -0
  106. package/src/tools/JsonForm/widgets/index.ts +14 -0
  107. package/src/tools/JsonTree/index.tsx +243 -0
  108. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +213 -0
  109. package/src/tools/LottiePlayer/index.tsx +56 -0
  110. package/src/tools/LottiePlayer/types.ts +108 -0
  111. package/src/tools/LottiePlayer/useLottie.ts +164 -0
  112. package/src/tools/Mermaid/Mermaid.client.tsx +82 -0
  113. package/src/tools/Mermaid/components/MermaidCodeViewer.tsx +95 -0
  114. package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +103 -0
  115. package/src/tools/Mermaid/hooks/index.ts +4 -0
  116. package/src/tools/Mermaid/hooks/useMermaidCleanup.ts +73 -0
  117. package/src/tools/Mermaid/hooks/useMermaidFullscreen.ts +46 -0
  118. package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +226 -0
  119. package/src/tools/Mermaid/hooks/useMermaidValidation.ts +29 -0
  120. package/src/tools/Mermaid/index.tsx +44 -0
  121. package/src/tools/Mermaid/utils/mermaid-helpers.ts +33 -0
  122. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +149 -0
  123. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +263 -0
  124. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +125 -0
  125. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +100 -0
  126. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +157 -0
  127. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  128. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +173 -0
  129. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +68 -0
  130. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  131. package/src/tools/OpenapiViewer/constants.ts +39 -0
  132. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +337 -0
  133. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  134. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  135. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +199 -0
  136. package/src/tools/OpenapiViewer/index.tsx +37 -0
  137. package/src/tools/OpenapiViewer/types.ts +151 -0
  138. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  139. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  140. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  141. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  142. package/src/tools/PrettyCode/PrettyCode.client.tsx +208 -0
  143. package/src/tools/PrettyCode/index.tsx +47 -0
  144. package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +91 -0
  145. package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +284 -0
  146. package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +141 -0
  147. package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +178 -0
  148. package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +95 -0
  149. package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +139 -0
  150. package/src/tools/VideoPlayer/README.md +264 -0
  151. package/src/tools/VideoPlayer/components/VideoControls.tsx +138 -0
  152. package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +172 -0
  153. package/src/tools/VideoPlayer/components/VideoPlayer.tsx +201 -0
  154. package/src/tools/VideoPlayer/components/index.ts +14 -0
  155. package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +52 -0
  156. package/src/tools/VideoPlayer/context/index.ts +8 -0
  157. package/src/tools/VideoPlayer/hooks/index.ts +12 -0
  158. package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +70 -0
  159. package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +116 -0
  160. package/src/tools/VideoPlayer/index.ts +77 -0
  161. package/src/tools/VideoPlayer/providers/NativeProvider.tsx +284 -0
  162. package/src/tools/VideoPlayer/providers/StreamProvider.tsx +505 -0
  163. package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +400 -0
  164. package/src/tools/VideoPlayer/providers/index.ts +8 -0
  165. package/src/tools/VideoPlayer/types/index.ts +38 -0
  166. package/src/tools/VideoPlayer/types/player.ts +116 -0
  167. package/src/tools/VideoPlayer/types/provider.ts +93 -0
  168. package/src/tools/VideoPlayer/types/sources.ts +97 -0
  169. package/src/tools/VideoPlayer/utils/debug.ts +14 -0
  170. package/src/tools/VideoPlayer/utils/fileSource.ts +78 -0
  171. package/src/tools/VideoPlayer/utils/index.ts +12 -0
  172. package/src/tools/VideoPlayer/utils/resolvers.ts +75 -0
  173. package/src/tools/_shared.ts +29 -0
  174. package/src/tools/index.ts +172 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,4941 @@
1
+ export { useLottie } from './chunk-YFRNE2IR.mjs';
2
+ import { PlaygroundProvider, PrettyCode_default } from './chunk-M6P2FU7L.mjs';
3
+ export { JsonTree_default as JsonTree, PrettyCode_default as PrettyCode } from './chunk-M6P2FU7L.mjs';
4
+ import { __name, __require } from './chunk-37ZI6VD4.mjs';
5
+ import React17, { lazy, forwardRef, useRef, useState, useMemo, useEffect, useCallback, useImperativeHandle, createContext, memo, Suspense, useContext } from 'react';
6
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+ import consola from 'consola';
8
+ import { Loader2, RotateCcw, SkipBack, Pause, Play, SkipForward, VolumeX, Volume2, Repeat, ChevronDown, Plus, AlertCircle, Minimize, Maximize, FileVideo, RefreshCw, ZoomOut, ZoomIn, Maximize2, FlipHorizontal, FlipVertical, RotateCw, Expand, ImageIcon, Music } from 'lucide-react';
9
+ import { Button, Slider, Label, Collapsible, CollapsibleTrigger, CollapsibleContent, Alert, AlertTitle, AlertDescription, Input, Checkbox, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Switch, DownloadButton, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, CopyButton } from '@djangocfg/ui-core/components';
10
+ import Form from '@rjsf/core';
11
+ import validator from '@rjsf/validator-ajv8';
12
+ import { createLogger, createMediaLogger, generateOgImageUrl, cn } from '@djangocfg/ui-core/lib';
13
+ import { getUiOptions, getTemplate, getInputProps } from '@rjsf/utils';
14
+ import '@vidstack/react/player/styles/base.css';
15
+ import '@vidstack/react/player/styles/default/theme.css';
16
+ import '@vidstack/react/player/styles/default/layouts/video.css';
17
+ import { MediaPlayer, MediaProvider, Poster, useMediaStore, useMediaRemote } from '@vidstack/react';
18
+ import { DefaultVideoLayout, defaultLayoutIcons } from '@vidstack/react/player/layouts/default';
19
+ import { create } from 'zustand';
20
+ import { devtools, persist } from 'zustand/middleware';
21
+ import { useShallow } from 'zustand/react/shallow';
22
+ import { Preloader, AspectRatio, cn as cn$1, Alert as Alert$1, AlertDescription as AlertDescription$1, Dialog, DialogContent, DialogTitle } from '@djangocfg/ui-core';
23
+ import { useLocalStorage, useResolvedTheme } from '@djangocfg/ui-core/hooks';
24
+ import { useControls, TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch';
25
+ import ReactMarkdown from 'react-markdown';
26
+ import remarkGfm from 'remark-gfm';
27
+
28
+ var MermaidClient = lazy(() => import('./Mermaid.client-4TU2TSH3.mjs'));
29
+ var LoadingFallback = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsxs("div", { className: "relative bg-card rounded-sm border border-border overflow-hidden", children: [
30
+ /* @__PURE__ */ jsxs("div", { className: "p-4 border-b border-border bg-muted/50", children: [
31
+ /* @__PURE__ */ jsx("h6", { className: "text-sm font-semibold text-foreground", children: "Diagram" }),
32
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Loading..." })
33
+ ] }),
34
+ /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("div", { className: "flex justify-center items-center min-h-[200px]", children: /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary" }) }) })
35
+ ] }), "LoadingFallback");
36
+ var Mermaid = /* @__PURE__ */ __name((props) => {
37
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(LoadingFallback, {}), children: /* @__PURE__ */ jsx(MermaidClient, { ...props }) });
38
+ }, "Mermaid");
39
+ var Mermaid_default = Mermaid;
40
+ var LottiePlayerClient = lazy(
41
+ () => import('./LottiePlayer.client-LBEC2JKY.mjs').then((mod) => ({ default: mod.LottiePlayer }))
42
+ );
43
+ var LoadingFallback2 = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
44
+ /* @__PURE__ */ jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-gray-900" }),
45
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: "Loading player..." })
46
+ ] }) }), "LoadingFallback");
47
+ function LottiePlayer(props) {
48
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(LoadingFallback2, {}), children: /* @__PURE__ */ jsx(LottiePlayerClient, { ...props }) });
49
+ }
50
+ __name(LottiePlayer, "LottiePlayer");
51
+ function FieldTemplate(props) {
52
+ const {
53
+ id,
54
+ classNames,
55
+ style,
56
+ label,
57
+ help,
58
+ required,
59
+ description,
60
+ errors,
61
+ children,
62
+ displayLabel,
63
+ hidden,
64
+ rawErrors
65
+ } = props;
66
+ if (hidden) {
67
+ return /* @__PURE__ */ jsx("div", { className: "hidden", children });
68
+ }
69
+ const hasError = rawErrors && rawErrors.length > 0;
70
+ return /* @__PURE__ */ jsxs(
71
+ "div",
72
+ {
73
+ className: cn("space-y-2", classNames),
74
+ style,
75
+ children: [
76
+ displayLabel && label && /* @__PURE__ */ jsxs(Label, { htmlFor: id, className: cn(hasError && "text-destructive"), children: [
77
+ label,
78
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
79
+ ] }),
80
+ description && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: description }),
81
+ /* @__PURE__ */ jsx("div", { children }),
82
+ errors && /* @__PURE__ */ jsx("div", { className: "text-sm text-destructive", children: errors }),
83
+ help && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: help })
84
+ ]
85
+ }
86
+ );
87
+ }
88
+ __name(FieldTemplate, "FieldTemplate");
89
+ function ObjectFieldTemplate(props) {
90
+ const {
91
+ title,
92
+ description,
93
+ properties,
94
+ required,
95
+ uiSchema
96
+ } = props;
97
+ const isCollapsible = uiSchema?.["ui:collapsible"] === true;
98
+ const defaultCollapsed = uiSchema?.["ui:collapsed"] === true;
99
+ const gridCols = uiSchema?.["ui:grid"];
100
+ const className = uiSchema?.["ui:className"];
101
+ const [isOpen, setIsOpen] = useState(!defaultCollapsed);
102
+ const isRoot = !title;
103
+ const gridClass = gridCols ? `grid gap-4 grid-cols-${gridCols}` : "space-y-4";
104
+ const content = /* @__PURE__ */ jsx("div", { className: cn(gridClass, className), children: properties.map((element) => /* @__PURE__ */ jsx("div", { className: "property-wrapper", children: element.content }, element.name)) });
105
+ if (isRoot) {
106
+ return /* @__PURE__ */ jsx("div", { className: "space-y-6", children: content });
107
+ }
108
+ if (isCollapsible) {
109
+ return /* @__PURE__ */ jsxs(Collapsible, { open: isOpen, onOpenChange: setIsOpen, className: "space-y-2", children: [
110
+ /* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "flex w-full items-center justify-between rounded-lg border bg-muted/50 px-4 py-3 text-left hover:bg-muted transition-colors", children: [
111
+ /* @__PURE__ */ jsxs("div", { children: [
112
+ /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold", children: [
113
+ title,
114
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
115
+ ] }),
116
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: description })
117
+ ] }),
118
+ /* @__PURE__ */ jsx(
119
+ ChevronDown,
120
+ {
121
+ className: cn(
122
+ "h-4 w-4 text-muted-foreground transition-transform duration-200",
123
+ isOpen && "rotate-180"
124
+ )
125
+ }
126
+ )
127
+ ] }),
128
+ /* @__PURE__ */ jsx(CollapsibleContent, { className: "pl-1 pr-1 pt-2", children: content })
129
+ ] });
130
+ }
131
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
132
+ title && /* @__PURE__ */ jsxs("div", { className: "border-b pb-2", children: [
133
+ /* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold", children: [
134
+ title,
135
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
136
+ ] }),
137
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: description })
138
+ ] }),
139
+ content
140
+ ] });
141
+ }
142
+ __name(ObjectFieldTemplate, "ObjectFieldTemplate");
143
+ function ArrayFieldTemplate(props) {
144
+ const {
145
+ title,
146
+ items,
147
+ canAdd,
148
+ onAddClick,
149
+ required
150
+ } = props;
151
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
152
+ title && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
153
+ /* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold", children: [
154
+ title,
155
+ required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
156
+ ] }),
157
+ canAdd && /* @__PURE__ */ jsxs(
158
+ Button,
159
+ {
160
+ type: "button",
161
+ variant: "outline",
162
+ size: "sm",
163
+ onClick: onAddClick,
164
+ className: "gap-2",
165
+ children: [
166
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
167
+ "Add Item"
168
+ ]
169
+ }
170
+ )
171
+ ] }),
172
+ /* @__PURE__ */ jsx("div", { className: "space-y-3", children: items }),
173
+ items.length === 0 && /* @__PURE__ */ jsxs("div", { className: "text-center py-8 text-muted-foreground border-2 border-dashed rounded-md", children: [
174
+ "No items added yet.",
175
+ canAdd && /* @__PURE__ */ jsxs(
176
+ Button,
177
+ {
178
+ type: "button",
179
+ variant: "ghost",
180
+ size: "sm",
181
+ onClick: onAddClick,
182
+ className: "mt-2 gap-2",
183
+ children: [
184
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
185
+ "Add First Item"
186
+ ]
187
+ }
188
+ )
189
+ ] })
190
+ ] });
191
+ }
192
+ __name(ArrayFieldTemplate, "ArrayFieldTemplate");
193
+ function ArrayFieldItemTemplate(props) {
194
+ const {
195
+ children,
196
+ className,
197
+ buttonsProps,
198
+ hasToolbar,
199
+ registry,
200
+ uiSchema
201
+ } = props;
202
+ const uiOptions = getUiOptions(uiSchema);
203
+ const ArrayFieldItemButtonsTemplate = getTemplate(
204
+ "ArrayFieldItemButtonsTemplate",
205
+ registry,
206
+ uiOptions
207
+ );
208
+ return /* @__PURE__ */ jsxs(
209
+ "div",
210
+ {
211
+ className: cn(
212
+ "flex gap-2 items-start p-4 rounded-md border bg-card",
213
+ className
214
+ ),
215
+ children: [
216
+ /* @__PURE__ */ jsx("div", { className: "flex-1", children }),
217
+ hasToolbar && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(ArrayFieldItemButtonsTemplate, { ...buttonsProps }) })
218
+ ]
219
+ }
220
+ );
221
+ }
222
+ __name(ArrayFieldItemTemplate, "ArrayFieldItemTemplate");
223
+ function ErrorListTemplate(props) {
224
+ const { errors } = props;
225
+ if (!errors || errors.length === 0) {
226
+ return null;
227
+ }
228
+ return /* @__PURE__ */ jsxs(Alert, { variant: "destructive", className: "mb-6", children: [
229
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
230
+ /* @__PURE__ */ jsx(AlertTitle, { children: "Validation Errors" }),
231
+ /* @__PURE__ */ jsx(AlertDescription, { children: /* @__PURE__ */ jsx("ul", { className: "list-disc list-inside space-y-1 mt-2", children: errors.map((error, index) => /* @__PURE__ */ jsx("li", { className: "text-sm", children: error.stack }, index)) }) })
232
+ ] });
233
+ }
234
+ __name(ErrorListTemplate, "ErrorListTemplate");
235
+ function BaseInputTemplate(props) {
236
+ const {
237
+ id,
238
+ type,
239
+ value,
240
+ readonly,
241
+ disabled,
242
+ autofocus,
243
+ onBlur,
244
+ onFocus,
245
+ onChange,
246
+ options,
247
+ schema,
248
+ rawErrors,
249
+ placeholder
250
+ } = props;
251
+ const inputProps = useMemo(() => {
252
+ return getInputProps(schema, type, options);
253
+ }, [schema, type, options]);
254
+ const safeValue = useMemo(() => {
255
+ if (value === null || value === void 0) return "";
256
+ return String(value);
257
+ }, [value]);
258
+ const hasError = useMemo(() => {
259
+ return rawErrors && rawErrors.length > 0;
260
+ }, [rawErrors]);
261
+ const handleChange = useCallback((event) => {
262
+ const val = event.target.value;
263
+ if (val === "") {
264
+ onChange(options?.emptyValue ?? "");
265
+ return;
266
+ }
267
+ if (inputProps.type === "number" || schema.type === "number" || schema.type === "integer") {
268
+ const num = Number(val);
269
+ onChange(isNaN(num) ? val : num);
270
+ return;
271
+ }
272
+ onChange(val);
273
+ }, [onChange, inputProps.type, schema.type, options?.emptyValue]);
274
+ const handleBlur = useCallback((event) => {
275
+ onBlur(id, event.target.value);
276
+ }, [id, onBlur]);
277
+ const handleFocus = useCallback((event) => {
278
+ onFocus(id, event.target.value);
279
+ }, [id, onFocus]);
280
+ const inputType = useMemo(() => {
281
+ if (inputProps.type) return inputProps.type;
282
+ if (schema.type === "number" || schema.type === "integer") return "number";
283
+ return "text";
284
+ }, [inputProps.type, schema.type]);
285
+ return /* @__PURE__ */ jsx(
286
+ Input,
287
+ {
288
+ id,
289
+ type: inputType,
290
+ value: safeValue,
291
+ disabled,
292
+ readOnly: readonly,
293
+ autoFocus: autofocus,
294
+ onChange: handleChange,
295
+ onBlur: handleBlur,
296
+ onFocus: handleFocus,
297
+ placeholder,
298
+ className: hasError ? "border-destructive" : "",
299
+ step: inputProps.step,
300
+ min: inputProps.min,
301
+ max: inputProps.max
302
+ }
303
+ );
304
+ }
305
+ __name(BaseInputTemplate, "BaseInputTemplate");
306
+ function validateSchema(schema) {
307
+ if (!schema || typeof schema !== "object") {
308
+ {
309
+ consola.error("[JsonSchemaForm] Invalid schema: must be an object", schema);
310
+ }
311
+ return null;
312
+ }
313
+ const hasValidStructure = schema.type || schema.properties || schema.$ref || schema.$schema;
314
+ if (!hasValidStructure) {
315
+ {
316
+ consola.error("[JsonSchemaForm] Invalid schema: missing type, properties, $ref, or $schema", schema);
317
+ }
318
+ return null;
319
+ }
320
+ {
321
+ consola.success("[JsonSchemaForm] Schema validated successfully:", {
322
+ type: schema.type,
323
+ title: schema.title,
324
+ hasProperties: !!schema.properties,
325
+ hasRequired: !!schema.required
326
+ });
327
+ }
328
+ return schema;
329
+ }
330
+ __name(validateSchema, "validateSchema");
331
+ function normalizeFormData(formData, schema) {
332
+ if (formData === null || formData === void 0) {
333
+ return schema.type === "object" ? {} : schema.type === "array" ? [] : null;
334
+ }
335
+ const normalized = JSON.parse(JSON.stringify(formData));
336
+ return removeUndefined(normalized);
337
+ }
338
+ __name(normalizeFormData, "normalizeFormData");
339
+ function removeUndefined(obj) {
340
+ if (obj === null || obj === void 0) {
341
+ return obj;
342
+ }
343
+ if (Array.isArray(obj)) {
344
+ return obj.map(removeUndefined).filter((item) => item !== void 0);
345
+ }
346
+ if (typeof obj === "object") {
347
+ const cleaned = {};
348
+ for (const key in obj) {
349
+ if (obj[key] !== void 0) {
350
+ cleaned[key] = removeUndefined(obj[key]);
351
+ }
352
+ }
353
+ return cleaned;
354
+ }
355
+ return obj;
356
+ }
357
+ __name(removeUndefined, "removeUndefined");
358
+ function mergeDefaults(formData, schema) {
359
+ if (!schema) return formData;
360
+ const result = { ...formData };
361
+ if (schema.type === "object" && schema.properties) {
362
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
363
+ const prop = propSchema;
364
+ if (result[key] === void 0 && prop.default !== void 0) {
365
+ result[key] = prop.default;
366
+ }
367
+ if (prop.type === "object" && result[key]) {
368
+ result[key] = mergeDefaults(result[key], prop);
369
+ }
370
+ }
371
+ }
372
+ return result;
373
+ }
374
+ __name(mergeDefaults, "mergeDefaults");
375
+ function safeJsonParse(jsonString, fallback) {
376
+ try {
377
+ return JSON.parse(jsonString);
378
+ } catch (error) {
379
+ consola.error("[JsonSchemaForm] JSON parse error:", error);
380
+ return fallback;
381
+ }
382
+ }
383
+ __name(safeJsonParse, "safeJsonParse");
384
+ function safeJsonStringify(obj, pretty = true) {
385
+ try {
386
+ return JSON.stringify(obj, null, pretty ? 2 : 0);
387
+ } catch (error) {
388
+ consola.error("[JsonSchemaForm] JSON stringify error:", error);
389
+ return "{}";
390
+ }
391
+ }
392
+ __name(safeJsonStringify, "safeJsonStringify");
393
+ function hasRequiredFields(schema) {
394
+ return Array.isArray(schema.required) && schema.required.length > 0;
395
+ }
396
+ __name(hasRequiredFields, "hasRequiredFields");
397
+ function getRequiredFields(schema, prefix = "") {
398
+ const required = [];
399
+ if (schema.required && Array.isArray(schema.required)) {
400
+ required.push(...schema.required.map(
401
+ (field) => prefix ? `${prefix}.${field}` : field
402
+ ));
403
+ }
404
+ if (schema.type === "object" && schema.properties) {
405
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
406
+ const prop = propSchema;
407
+ const fieldPath = prefix ? `${prefix}.${key}` : key;
408
+ required.push(...getRequiredFields(prop, fieldPath));
409
+ }
410
+ }
411
+ return required;
412
+ }
413
+ __name(getRequiredFields, "getRequiredFields");
414
+ function validateRequiredFields(formData, schema) {
415
+ const requiredFields = getRequiredFields(schema);
416
+ const missing = [];
417
+ for (const field of requiredFields) {
418
+ const value = getNestedValue(formData, field);
419
+ if (value === void 0 || value === null || value === "") {
420
+ missing.push(field);
421
+ }
422
+ }
423
+ return {
424
+ valid: missing.length === 0,
425
+ missing
426
+ };
427
+ }
428
+ __name(validateRequiredFields, "validateRequiredFields");
429
+ function getNestedValue(obj, path) {
430
+ return path.split(".").reduce((current, key) => current?.[key], obj);
431
+ }
432
+ __name(getNestedValue, "getNestedValue");
433
+ function TextWidget(props) {
434
+ const {
435
+ id,
436
+ placeholder,
437
+ required,
438
+ disabled,
439
+ readonly,
440
+ autofocus,
441
+ value,
442
+ onChange,
443
+ onBlur,
444
+ onFocus,
445
+ options,
446
+ schema,
447
+ rawErrors
448
+ } = props;
449
+ const config = useMemo(() => ({
450
+ isTextarea: options?.widget === "textarea",
451
+ rows: options?.rows || 3,
452
+ emptyValue: options?.emptyValue
453
+ }), [options]);
454
+ const safeValue = useMemo(() => {
455
+ if (value === null || value === void 0) return "";
456
+ return String(value);
457
+ }, [value]);
458
+ const hasError = useMemo(() => {
459
+ return rawErrors && rawErrors.length > 0;
460
+ }, [rawErrors]);
461
+ const handleChange = useCallback((event) => {
462
+ const newValue = event.target.value;
463
+ onChange(newValue === "" ? config.emptyValue : newValue);
464
+ }, [onChange, config.emptyValue]);
465
+ const handleBlur = useCallback((event) => {
466
+ onBlur(id, event.target.value);
467
+ }, [id, onBlur]);
468
+ const handleFocus = useCallback((event) => {
469
+ onFocus(id, event.target.value);
470
+ }, [id, onFocus]);
471
+ if (config.isTextarea) {
472
+ return /* @__PURE__ */ jsx(
473
+ "textarea",
474
+ {
475
+ id,
476
+ className: `flex min-h-[80px] w-full rounded-md border ${hasError ? "border-destructive" : "border-input"} bg-transparent px-3 py-2 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm`,
477
+ placeholder,
478
+ disabled: disabled || readonly,
479
+ readOnly: readonly,
480
+ autoFocus: autofocus,
481
+ value: safeValue,
482
+ required,
483
+ onChange: handleChange,
484
+ onBlur: handleBlur,
485
+ onFocus: handleFocus,
486
+ rows: config.rows
487
+ }
488
+ );
489
+ }
490
+ return /* @__PURE__ */ jsx(
491
+ Input,
492
+ {
493
+ id,
494
+ type: "text",
495
+ placeholder,
496
+ disabled,
497
+ readOnly: readonly,
498
+ autoFocus: autofocus,
499
+ value: safeValue,
500
+ required,
501
+ onChange: handleChange,
502
+ onBlur: handleBlur,
503
+ onFocus: handleFocus,
504
+ className: hasError ? "border-destructive" : ""
505
+ }
506
+ );
507
+ }
508
+ __name(TextWidget, "TextWidget");
509
+ function NumberWidget(props) {
510
+ const {
511
+ id,
512
+ placeholder,
513
+ required,
514
+ disabled,
515
+ readonly,
516
+ autofocus,
517
+ value,
518
+ onChange,
519
+ onBlur,
520
+ onFocus,
521
+ options,
522
+ schema,
523
+ rawErrors
524
+ } = props;
525
+ const config = useMemo(() => ({
526
+ isInteger: schema.type === "integer",
527
+ step: schema.type === "integer" ? "1" : "any",
528
+ min: schema.minimum,
529
+ max: schema.maximum,
530
+ emptyValue: options?.emptyValue
531
+ }), [schema, options]);
532
+ const safeValue = useMemo(() => {
533
+ if (value === null || value === void 0 || value === "") return "";
534
+ if (typeof value === "number" && !isNaN(value)) return value;
535
+ return "";
536
+ }, [value]);
537
+ const hasError = useMemo(() => {
538
+ return rawErrors && rawErrors.length > 0;
539
+ }, [rawErrors]);
540
+ const handleChange = useCallback((event) => {
541
+ const newValue = event.target.value;
542
+ if (newValue === "") {
543
+ onChange(config.emptyValue);
544
+ } else {
545
+ const parsedValue = config.isInteger ? parseInt(newValue, 10) : parseFloat(newValue);
546
+ onChange(isNaN(parsedValue) ? config.emptyValue : parsedValue);
547
+ }
548
+ }, [onChange, config]);
549
+ const handleBlur = useCallback((event) => {
550
+ onBlur(id, event.target.value);
551
+ }, [id, onBlur]);
552
+ const handleFocus = useCallback((event) => {
553
+ onFocus(id, event.target.value);
554
+ }, [id, onFocus]);
555
+ return /* @__PURE__ */ jsx(
556
+ Input,
557
+ {
558
+ id,
559
+ type: "number",
560
+ placeholder,
561
+ disabled,
562
+ readOnly: readonly,
563
+ autoFocus: autofocus,
564
+ value: safeValue,
565
+ required,
566
+ onChange: handleChange,
567
+ onBlur: handleBlur,
568
+ onFocus: handleFocus,
569
+ step: config.step,
570
+ min: config.min,
571
+ max: config.max,
572
+ className: hasError ? "border-destructive" : ""
573
+ }
574
+ );
575
+ }
576
+ __name(NumberWidget, "NumberWidget");
577
+ function CheckboxWidget(props) {
578
+ const {
579
+ id,
580
+ value,
581
+ disabled,
582
+ readonly,
583
+ autofocus,
584
+ onChange,
585
+ onBlur,
586
+ onFocus
587
+ } = props;
588
+ const handleChange = /* @__PURE__ */ __name((checked) => {
589
+ onChange(checked);
590
+ }, "handleChange");
591
+ return /* @__PURE__ */ jsx(
592
+ Checkbox,
593
+ {
594
+ id,
595
+ checked: value || false,
596
+ disabled: disabled || readonly,
597
+ autoFocus: autofocus,
598
+ onCheckedChange: handleChange,
599
+ onBlur: () => onBlur(id, value),
600
+ onFocus: () => onFocus(id, value)
601
+ }
602
+ );
603
+ }
604
+ __name(CheckboxWidget, "CheckboxWidget");
605
+ function SelectWidget(props) {
606
+ const {
607
+ id,
608
+ options,
609
+ value,
610
+ required,
611
+ disabled,
612
+ readonly,
613
+ autofocus,
614
+ onChange,
615
+ onBlur,
616
+ onFocus,
617
+ placeholder,
618
+ rawErrors
619
+ } = props;
620
+ const enumOptions = useMemo(() => {
621
+ const opts = options?.enumOptions;
622
+ if (!Array.isArray(opts)) return [];
623
+ return opts.filter((opt) => opt && opt.value !== void 0);
624
+ }, [options]);
625
+ const hasError = useMemo(() => {
626
+ return rawErrors && rawErrors.length > 0;
627
+ }, [rawErrors]);
628
+ const safeValue = useMemo(() => {
629
+ if (value === null || value === void 0) return "";
630
+ return String(value);
631
+ }, [value]);
632
+ const handleChange = useCallback((newValue) => {
633
+ onChange(newValue);
634
+ onBlur(id, newValue);
635
+ }, [onChange, onBlur, id]);
636
+ if (enumOptions.length === 0) {
637
+ return /* @__PURE__ */ jsx(
638
+ "input",
639
+ {
640
+ id,
641
+ type: "text",
642
+ value: safeValue,
643
+ onChange: (e) => onChange(e.target.value),
644
+ onBlur: (e) => onBlur(id, e.target.value),
645
+ onFocus: (e) => onFocus(id, e.target.value),
646
+ disabled: disabled || readonly,
647
+ readOnly: readonly,
648
+ className: `flex h-10 w-full rounded-md border ${hasError ? "border-destructive" : "border-input"} bg-transparent px-3 py-2 text-base shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm`,
649
+ placeholder
650
+ }
651
+ );
652
+ }
653
+ return /* @__PURE__ */ jsxs(
654
+ Select,
655
+ {
656
+ value: safeValue,
657
+ onValueChange: handleChange,
658
+ disabled: disabled || readonly,
659
+ required,
660
+ children: [
661
+ /* @__PURE__ */ jsx(
662
+ SelectTrigger,
663
+ {
664
+ id,
665
+ className: hasError ? "border-destructive" : "",
666
+ autoFocus: autofocus,
667
+ onFocus: () => onFocus(id, value),
668
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: placeholder || "Select an option" })
669
+ }
670
+ ),
671
+ /* @__PURE__ */ jsx(SelectContent, { children: enumOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: String(option.value), children: option.label || String(option.value) }, String(option.value))) })
672
+ ]
673
+ }
674
+ );
675
+ }
676
+ __name(SelectWidget, "SelectWidget");
677
+ function SwitchWidget(props) {
678
+ const {
679
+ id,
680
+ value,
681
+ disabled,
682
+ readonly,
683
+ onChange,
684
+ onBlur,
685
+ onFocus
686
+ } = props;
687
+ const handleChange = /* @__PURE__ */ __name((checked) => {
688
+ onChange(checked);
689
+ }, "handleChange");
690
+ return /* @__PURE__ */ jsx(
691
+ Switch,
692
+ {
693
+ id,
694
+ checked: value || false,
695
+ disabled: disabled || readonly,
696
+ onCheckedChange: handleChange,
697
+ onBlur: () => onBlur(id, value),
698
+ onFocus: () => onFocus(id, value)
699
+ }
700
+ );
701
+ }
702
+ __name(SwitchWidget, "SwitchWidget");
703
+ function ColorWidget(props) {
704
+ const {
705
+ id,
706
+ placeholder,
707
+ required,
708
+ disabled,
709
+ readonly,
710
+ autofocus,
711
+ value,
712
+ onChange,
713
+ onBlur,
714
+ onFocus,
715
+ options,
716
+ rawErrors
717
+ } = props;
718
+ const colorInputRef = useRef(null);
719
+ const format = useMemo(() => {
720
+ if (options?.format) return options.format;
721
+ if (typeof value === "string" && value.startsWith("#")) return "hex";
722
+ return "hsl";
723
+ }, [options?.format, value]);
724
+ const safeValue = useMemo(() => {
725
+ if (value === null || value === void 0) return "";
726
+ return String(value);
727
+ }, [value]);
728
+ const hslToCss = useCallback((hslValue) => {
729
+ if (!hslValue) return "transparent";
730
+ if (hslValue.startsWith("#")) return hslValue;
731
+ if (hslValue.startsWith("hsl")) return hslValue;
732
+ const parts = hslValue.split(" ");
733
+ if (parts.length === 3) {
734
+ return `hsl(${parts[0]}, ${parts[1]}, ${parts[2]})`;
735
+ }
736
+ return "transparent";
737
+ }, []);
738
+ const previewColor = useMemo(() => {
739
+ return hslToCss(safeValue);
740
+ }, [safeValue, hslToCss]);
741
+ const hexToHsl = useCallback((hex) => {
742
+ hex = hex.replace("#", "");
743
+ const r = parseInt(hex.substring(0, 2), 16) / 255;
744
+ const g = parseInt(hex.substring(2, 4), 16) / 255;
745
+ const b = parseInt(hex.substring(4, 6), 16) / 255;
746
+ const max = Math.max(r, g, b);
747
+ const min = Math.min(r, g, b);
748
+ let h = 0;
749
+ let s = 0;
750
+ const l = (max + min) / 2;
751
+ if (max !== min) {
752
+ const d = max - min;
753
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
754
+ switch (max) {
755
+ case r:
756
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
757
+ break;
758
+ case g:
759
+ h = ((b - r) / d + 2) / 6;
760
+ break;
761
+ case b:
762
+ h = ((r - g) / d + 4) / 6;
763
+ break;
764
+ }
765
+ }
766
+ return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
767
+ }, []);
768
+ const hslToHex = useCallback((hslValue) => {
769
+ if (!hslValue || hslValue.startsWith("#")) return hslValue || "#000000";
770
+ const parts = hslValue.split(" ");
771
+ if (parts.length !== 3) return "#000000";
772
+ const h = parseInt(parts[0]) / 360;
773
+ const s = parseInt(parts[1].replace("%", "")) / 100;
774
+ const l = parseInt(parts[2].replace("%", "")) / 100;
775
+ const hue2rgb = /* @__PURE__ */ __name((p, q, t) => {
776
+ if (t < 0) t += 1;
777
+ if (t > 1) t -= 1;
778
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
779
+ if (t < 1 / 2) return q;
780
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
781
+ return p;
782
+ }, "hue2rgb");
783
+ let r, g, b;
784
+ if (s === 0) {
785
+ r = g = b = l;
786
+ } else {
787
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
788
+ const p = 2 * l - q;
789
+ r = hue2rgb(p, q, h + 1 / 3);
790
+ g = hue2rgb(p, q, h);
791
+ b = hue2rgb(p, q, h - 1 / 3);
792
+ }
793
+ const toHex = /* @__PURE__ */ __name((x) => {
794
+ const hex = Math.round(x * 255).toString(16);
795
+ return hex.length === 1 ? "0" + hex : hex;
796
+ }, "toHex");
797
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
798
+ }, []);
799
+ const hasError = useMemo(() => {
800
+ return rawErrors && rawErrors.length > 0;
801
+ }, [rawErrors]);
802
+ const handleChange = useCallback((event) => {
803
+ const newValue = event.target.value;
804
+ onChange(newValue);
805
+ }, [onChange]);
806
+ const handleColorPickerChange = useCallback((event) => {
807
+ const hexValue2 = event.target.value;
808
+ if (format === "hsl") {
809
+ onChange(hexToHsl(hexValue2));
810
+ } else {
811
+ onChange(hexValue2);
812
+ }
813
+ }, [onChange, format, hexToHsl]);
814
+ const handleBlur = useCallback((event) => {
815
+ onBlur(id, event.target.value);
816
+ }, [id, onBlur]);
817
+ const handleFocus = useCallback((event) => {
818
+ onFocus(id, event.target.value);
819
+ }, [id, onFocus]);
820
+ const hexValue = useMemo(() => {
821
+ if (format === "hex" || safeValue.startsWith("#")) {
822
+ return safeValue || "#000000";
823
+ }
824
+ return hslToHex(safeValue);
825
+ }, [safeValue, format, hslToHex]);
826
+ const handleColorBoxClick = useCallback(() => {
827
+ if (!disabled && !readonly) {
828
+ colorInputRef.current?.click();
829
+ }
830
+ }, [disabled, readonly]);
831
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
832
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
833
+ /* @__PURE__ */ jsx(
834
+ "button",
835
+ {
836
+ type: "button",
837
+ onClick: handleColorBoxClick,
838
+ disabled: disabled || readonly,
839
+ className: "h-10 w-10 shrink-0 rounded-md border-2 border-input cursor-pointer hover:border-ring focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
840
+ style: { backgroundColor: previewColor },
841
+ "aria-label": "Pick color"
842
+ }
843
+ ),
844
+ /* @__PURE__ */ jsx(
845
+ "input",
846
+ {
847
+ ref: colorInputRef,
848
+ type: "color",
849
+ value: hexValue,
850
+ onChange: handleColorPickerChange,
851
+ className: "absolute inset-0 opacity-0 w-full h-full cursor-pointer",
852
+ disabled: disabled || readonly,
853
+ tabIndex: -1
854
+ }
855
+ )
856
+ ] }),
857
+ /* @__PURE__ */ jsx(
858
+ Input,
859
+ {
860
+ id,
861
+ type: "text",
862
+ placeholder: placeholder || (format === "hsl" ? "217 91% 60%" : "#3b82f6"),
863
+ disabled,
864
+ readOnly: readonly,
865
+ autoFocus: autofocus,
866
+ value: safeValue,
867
+ required,
868
+ onChange: handleChange,
869
+ onBlur: handleBlur,
870
+ onFocus: handleFocus,
871
+ className: `flex-1 font-mono text-sm ${hasError ? "border-destructive" : ""}`
872
+ }
873
+ )
874
+ ] });
875
+ }
876
+ __name(ColorWidget, "ColorWidget");
877
+ function SliderWidget(props) {
878
+ const {
879
+ id,
880
+ disabled,
881
+ readonly,
882
+ value,
883
+ onChange,
884
+ schema,
885
+ options,
886
+ rawErrors
887
+ } = props;
888
+ const config = useMemo(() => {
889
+ const min = schema.minimum ?? options?.min ?? 0;
890
+ const max = schema.maximum ?? options?.max ?? 100;
891
+ const step = options?.step ?? (schema.type === "integer" ? 1 : 0.1);
892
+ const unit = options?.unit;
893
+ const showInput = options?.showInput !== false;
894
+ return { min, max, step, unit, showInput };
895
+ }, [schema, options]);
896
+ const numericValue = useMemo(() => {
897
+ if (value === null || value === void 0 || value === "") {
898
+ return config.min;
899
+ }
900
+ if (typeof value === "number") {
901
+ return value;
902
+ }
903
+ if (typeof value === "string") {
904
+ const parsed = parseFloat(value);
905
+ return isNaN(parsed) ? config.min : parsed;
906
+ }
907
+ return config.min;
908
+ }, [value, config.min]);
909
+ const hasError = useMemo(() => {
910
+ return rawErrors && rawErrors.length > 0;
911
+ }, [rawErrors]);
912
+ const handleSliderChange = useCallback((values) => {
913
+ const newValue = values[0];
914
+ if (config.unit) {
915
+ onChange(`${newValue}${config.unit}`);
916
+ } else {
917
+ onChange(newValue);
918
+ }
919
+ }, [onChange, config.unit]);
920
+ const handleInputChange = useCallback((event) => {
921
+ const inputValue = event.target.value;
922
+ if (config.unit) {
923
+ const cleanValue = inputValue.replace(config.unit, "").trim();
924
+ const parsed = parseFloat(cleanValue);
925
+ if (!isNaN(parsed)) {
926
+ onChange(`${parsed}${config.unit}`);
927
+ } else if (inputValue === "") {
928
+ onChange(`${config.min}${config.unit}`);
929
+ }
930
+ } else {
931
+ const parsed = parseFloat(inputValue);
932
+ onChange(isNaN(parsed) ? config.min : parsed);
933
+ }
934
+ }, [onChange, config.unit, config.min]);
935
+ const displayValue = useMemo(() => {
936
+ if (config.unit) {
937
+ return `${numericValue}${config.unit}`;
938
+ }
939
+ return String(numericValue);
940
+ }, [numericValue, config.unit]);
941
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-3", hasError && "text-destructive"), children: [
942
+ /* @__PURE__ */ jsx(
943
+ Slider,
944
+ {
945
+ id,
946
+ disabled: disabled || readonly,
947
+ value: [numericValue],
948
+ onValueChange: handleSliderChange,
949
+ min: config.min,
950
+ max: config.max,
951
+ step: config.step,
952
+ className: "flex-1"
953
+ }
954
+ ),
955
+ config.showInput ? /* @__PURE__ */ jsx(
956
+ Input,
957
+ {
958
+ type: "text",
959
+ value: displayValue,
960
+ onChange: handleInputChange,
961
+ disabled,
962
+ readOnly: readonly,
963
+ className: cn(
964
+ "w-20 text-center font-mono text-sm",
965
+ hasError && "border-destructive"
966
+ )
967
+ }
968
+ ) : /* @__PURE__ */ jsx("span", { className: "w-16 text-right font-mono text-sm text-muted-foreground", children: displayValue })
969
+ ] });
970
+ }
971
+ __name(SliderWidget, "SliderWidget");
972
+ function JsonSchemaForm(props) {
973
+ const {
974
+ schema,
975
+ uiSchema,
976
+ formData,
977
+ onSubmit,
978
+ onChange,
979
+ onError,
980
+ showErrorList = "top",
981
+ liveValidate = false,
982
+ disabled = false,
983
+ readonly = false,
984
+ className,
985
+ showSubmitButton = true,
986
+ submitButtonText = "Submit",
987
+ ...restProps
988
+ } = props;
989
+ const validatedSchema = useMemo(() => {
990
+ {
991
+ consola.info("[JsonSchemaForm] Validating schema...", schema);
992
+ }
993
+ const result = validateSchema(schema);
994
+ if (!result && true) {
995
+ consola.error("[JsonSchemaForm] Schema validation failed");
996
+ }
997
+ return result;
998
+ }, [schema]);
999
+ const normalizedFormData = useMemo(() => {
1000
+ if (!validatedSchema) {
1001
+ {
1002
+ consola.warn("[JsonSchemaForm] Cannot normalize formData - invalid schema");
1003
+ }
1004
+ return null;
1005
+ }
1006
+ {
1007
+ consola.info("[JsonSchemaForm] Normalizing formData...", formData);
1008
+ }
1009
+ const normalized = normalizeFormData(formData, validatedSchema);
1010
+ {
1011
+ consola.info("[JsonSchemaForm] Normalized formData:", normalized);
1012
+ }
1013
+ return normalized;
1014
+ }, [formData, validatedSchema]);
1015
+ const widgets = useMemo(() => ({
1016
+ // Standard widget names (PascalCase) - used by RJSF internally
1017
+ TextWidget,
1018
+ NumberWidget,
1019
+ CheckboxWidget,
1020
+ SelectWidget,
1021
+ SwitchWidget,
1022
+ ColorWidget,
1023
+ SliderWidget,
1024
+ // Lowercase aliases - for uiSchema 'ui:widget' references
1025
+ text: TextWidget,
1026
+ number: NumberWidget,
1027
+ checkbox: CheckboxWidget,
1028
+ select: SelectWidget,
1029
+ switch: SwitchWidget,
1030
+ color: ColorWidget,
1031
+ slider: SliderWidget,
1032
+ range: SliderWidget
1033
+ // alias
1034
+ }), []);
1035
+ const templates = useMemo(() => ({
1036
+ FieldTemplate,
1037
+ ObjectFieldTemplate,
1038
+ ArrayFieldTemplate,
1039
+ ArrayFieldItemTemplate,
1040
+ ErrorListTemplate,
1041
+ BaseInputTemplate
1042
+ }), []);
1043
+ const handleSubmit = useCallback((data) => {
1044
+ if (onSubmit) {
1045
+ const cleanData = {
1046
+ ...data,
1047
+ formData: normalizeFormData(data.formData, validatedSchema)
1048
+ };
1049
+ onSubmit(cleanData);
1050
+ }
1051
+ }, [onSubmit, validatedSchema]);
1052
+ const handleChange = useCallback((data) => {
1053
+ if (onChange) {
1054
+ onChange(data);
1055
+ }
1056
+ }, [onChange]);
1057
+ const handleError = useCallback((errors) => {
1058
+ if (onError) {
1059
+ onError(errors);
1060
+ }
1061
+ }, [onError]);
1062
+ if (!validatedSchema) {
1063
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
1064
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
1065
+ /* @__PURE__ */ jsx(AlertDescription, { children: "Invalid schema provided. Please check the schema format." })
1066
+ ] }) });
1067
+ }
1068
+ return /* @__PURE__ */ jsx("div", { className, children: /* @__PURE__ */ jsx(
1069
+ Form,
1070
+ {
1071
+ schema: validatedSchema,
1072
+ uiSchema,
1073
+ formData: normalizedFormData,
1074
+ validator,
1075
+ widgets,
1076
+ templates,
1077
+ onSubmit: handleSubmit,
1078
+ onChange: handleChange,
1079
+ onError: handleError,
1080
+ showErrorList,
1081
+ liveValidate,
1082
+ disabled,
1083
+ readonly,
1084
+ ...restProps,
1085
+ children: showSubmitButton && /* @__PURE__ */ jsx("div", { className: "mt-6 flex gap-2", children: /* @__PURE__ */ jsx(Button, { type: "submit", disabled, children: submitButtonText }) })
1086
+ }
1087
+ ) });
1088
+ }
1089
+ __name(JsonSchemaForm, "JsonSchemaForm");
1090
+ var PlaygroundLayout = lazy(
1091
+ () => import('./PlaygroundLayout-4DYBORAS.mjs').then((mod) => ({ default: mod.PlaygroundLayout }))
1092
+ );
1093
+ var LoadingFallback3 = /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-[400px]", children: /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: "Loading API Playground..." }) }), "LoadingFallback");
1094
+ var Playground = /* @__PURE__ */ __name(({ config }) => {
1095
+ return /* @__PURE__ */ jsx(PlaygroundProvider, { config, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(LoadingFallback3, {}), children: /* @__PURE__ */ jsx(PlaygroundLayout, {}) }) });
1096
+ }, "Playground");
1097
+ var OpenapiViewer_default = Playground;
1098
+ var cacheDebug = createLogger("MediaCache");
1099
+ var STREAM_URL_TTL = 30 * 1e3;
1100
+ var DEFAULT_VIDEO_SETTINGS = {
1101
+ volume: 1,
1102
+ isLooping: false
1103
+ };
1104
+ var initialState = {
1105
+ blobUrls: /* @__PURE__ */ new Map(),
1106
+ imageDimensions: /* @__PURE__ */ new Map(),
1107
+ audioPlaybackPositions: /* @__PURE__ */ new Map(),
1108
+ audioEffectConfigs: /* @__PURE__ */ new Map(),
1109
+ videoStreamUrls: /* @__PURE__ */ new Map(),
1110
+ videoPosterUrls: /* @__PURE__ */ new Map(),
1111
+ videoPlaybackPositions: /* @__PURE__ */ new Map(),
1112
+ videoMetadata: /* @__PURE__ */ new Map(),
1113
+ videoPlayerSettings: DEFAULT_VIDEO_SETTINGS
1114
+ };
1115
+ var useMediaCacheStore = create()(
1116
+ devtools(
1117
+ persist(
1118
+ (set, get) => ({
1119
+ ...initialState,
1120
+ // ========== Blob URL Management ==========
1121
+ getOrCreateBlobUrl: /* @__PURE__ */ __name((key, content, mimeType) => {
1122
+ const existing = get().blobUrls.get(key);
1123
+ if (existing) {
1124
+ cacheDebug.debug(`Blob URL reused: ${key}`, { refCount: existing.refCount + 1 });
1125
+ set(
1126
+ (state) => ({
1127
+ blobUrls: new Map(state.blobUrls).set(key, {
1128
+ ...existing,
1129
+ refCount: existing.refCount + 1
1130
+ })
1131
+ }),
1132
+ false,
1133
+ "blobUrl/incrementRef"
1134
+ );
1135
+ return existing.url;
1136
+ }
1137
+ const blob = new Blob([content], { type: mimeType });
1138
+ const url = URL.createObjectURL(blob);
1139
+ const sizeMB = (content.byteLength / 1024 / 1024).toFixed(2);
1140
+ cacheDebug.debug(`Blob URL created: ${key}`, { mimeType, size: `${sizeMB}MB` });
1141
+ set(
1142
+ (state) => ({
1143
+ blobUrls: new Map(state.blobUrls).set(key, {
1144
+ url,
1145
+ refCount: 1,
1146
+ createdAt: Date.now()
1147
+ })
1148
+ }),
1149
+ false,
1150
+ "blobUrl/create"
1151
+ );
1152
+ return url;
1153
+ }, "getOrCreateBlobUrl"),
1154
+ releaseBlobUrl: /* @__PURE__ */ __name((key) => {
1155
+ const entry = get().blobUrls.get(key);
1156
+ if (!entry) return;
1157
+ if (entry.refCount <= 1) {
1158
+ cacheDebug.debug(`Blob URL revoked: ${key}`);
1159
+ URL.revokeObjectURL(entry.url);
1160
+ set(
1161
+ (state) => {
1162
+ const newMap = new Map(state.blobUrls);
1163
+ newMap.delete(key);
1164
+ return { blobUrls: newMap };
1165
+ },
1166
+ false,
1167
+ "blobUrl/revoke"
1168
+ );
1169
+ } else {
1170
+ cacheDebug.debug(`Blob URL released: ${key}`, { refCount: entry.refCount - 1 });
1171
+ set(
1172
+ (state) => ({
1173
+ blobUrls: new Map(state.blobUrls).set(key, {
1174
+ ...entry,
1175
+ refCount: entry.refCount - 1
1176
+ })
1177
+ }),
1178
+ false,
1179
+ "blobUrl/decrementRef"
1180
+ );
1181
+ }
1182
+ }, "releaseBlobUrl"),
1183
+ hasBlobUrl: /* @__PURE__ */ __name((key) => get().blobUrls.has(key), "hasBlobUrl"),
1184
+ // ========== Image Cache ==========
1185
+ cacheDimensions: /* @__PURE__ */ __name((src, dims) => {
1186
+ set(
1187
+ (state) => ({
1188
+ imageDimensions: new Map(state.imageDimensions).set(src, dims)
1189
+ }),
1190
+ false,
1191
+ "image/cacheDimensions"
1192
+ );
1193
+ }, "cacheDimensions"),
1194
+ getDimensions: /* @__PURE__ */ __name((src) => get().imageDimensions.get(src) ?? null, "getDimensions"),
1195
+ // ========== Audio Cache ==========
1196
+ saveAudioPosition: /* @__PURE__ */ __name((uri, time) => {
1197
+ set(
1198
+ (state) => ({
1199
+ audioPlaybackPositions: new Map(state.audioPlaybackPositions).set(
1200
+ uri,
1201
+ time
1202
+ )
1203
+ }),
1204
+ false,
1205
+ "audio/savePosition"
1206
+ );
1207
+ }, "saveAudioPosition"),
1208
+ getAudioPosition: /* @__PURE__ */ __name((uri) => get().audioPlaybackPositions.get(uri) ?? null, "getAudioPosition"),
1209
+ getEffectConfig: /* @__PURE__ */ __name((key) => get().audioEffectConfigs.get(key) ?? null, "getEffectConfig"),
1210
+ cacheEffectConfig: /* @__PURE__ */ __name((key, config) => {
1211
+ set(
1212
+ (state) => ({
1213
+ audioEffectConfigs: new Map(state.audioEffectConfigs).set(
1214
+ key,
1215
+ config
1216
+ )
1217
+ }),
1218
+ false,
1219
+ "audio/cacheEffectConfig"
1220
+ );
1221
+ }, "cacheEffectConfig"),
1222
+ // ========== Video Cache ==========
1223
+ getOrCreateStreamUrl: /* @__PURE__ */ __name((sessionId, path, generator) => {
1224
+ const key = `${sessionId}:${path}`;
1225
+ const cached = get().videoStreamUrls.get(key);
1226
+ if (cached && Date.now() - cached.timestamp < STREAM_URL_TTL) {
1227
+ return cached.url;
1228
+ }
1229
+ const url = generator(sessionId, path);
1230
+ set(
1231
+ (state) => ({
1232
+ videoStreamUrls: new Map(state.videoStreamUrls).set(key, {
1233
+ url,
1234
+ timestamp: Date.now()
1235
+ })
1236
+ }),
1237
+ false,
1238
+ "video/cacheStreamUrl"
1239
+ );
1240
+ return url;
1241
+ }, "getOrCreateStreamUrl"),
1242
+ getPosterUrl: /* @__PURE__ */ __name((title) => get().videoPosterUrls.get(title) ?? null, "getPosterUrl"),
1243
+ cachePosterUrl: /* @__PURE__ */ __name((title, url) => {
1244
+ set(
1245
+ (state) => ({
1246
+ videoPosterUrls: new Map(state.videoPosterUrls).set(title, url)
1247
+ }),
1248
+ false,
1249
+ "video/cachePosterUrl"
1250
+ );
1251
+ }, "cachePosterUrl"),
1252
+ saveVideoPosition: /* @__PURE__ */ __name((key, time) => {
1253
+ set(
1254
+ (state) => ({
1255
+ videoPlaybackPositions: new Map(state.videoPlaybackPositions).set(
1256
+ key,
1257
+ time
1258
+ )
1259
+ }),
1260
+ false,
1261
+ "video/savePosition"
1262
+ );
1263
+ }, "saveVideoPosition"),
1264
+ getVideoPosition: /* @__PURE__ */ __name((key) => get().videoPlaybackPositions.get(key) ?? null, "getVideoPosition"),
1265
+ cacheVideoMetadata: /* @__PURE__ */ __name((url, meta) => {
1266
+ set(
1267
+ (state) => ({
1268
+ videoMetadata: new Map(state.videoMetadata).set(url, meta)
1269
+ }),
1270
+ false,
1271
+ "video/cacheMetadata"
1272
+ );
1273
+ }, "cacheVideoMetadata"),
1274
+ getVideoMetadata: /* @__PURE__ */ __name((url) => get().videoMetadata.get(url) ?? null, "getVideoMetadata"),
1275
+ invalidateSession: /* @__PURE__ */ __name((sessionId) => {
1276
+ set(
1277
+ (state) => {
1278
+ const newUrls = new Map(state.videoStreamUrls);
1279
+ for (const [key] of newUrls) {
1280
+ if (key.startsWith(`${sessionId}:`)) {
1281
+ newUrls.delete(key);
1282
+ }
1283
+ }
1284
+ return { videoStreamUrls: newUrls };
1285
+ },
1286
+ false,
1287
+ "video/invalidateSession"
1288
+ );
1289
+ }, "invalidateSession"),
1290
+ getVideoPlayerSettings: /* @__PURE__ */ __name(() => get().videoPlayerSettings, "getVideoPlayerSettings"),
1291
+ saveVideoPlayerSettings: /* @__PURE__ */ __name((settings) => {
1292
+ set(
1293
+ (state) => ({
1294
+ videoPlayerSettings: { ...state.videoPlayerSettings, ...settings }
1295
+ }),
1296
+ false,
1297
+ "video/savePlayerSettings"
1298
+ );
1299
+ }, "saveVideoPlayerSettings"),
1300
+ // ========== Global ==========
1301
+ clearCache: /* @__PURE__ */ __name(() => {
1302
+ const stats = get().getCacheStats();
1303
+ cacheDebug.info("Clearing cache", stats);
1304
+ get().blobUrls.forEach(({ url }) => URL.revokeObjectURL(url));
1305
+ set(initialState, false, "clearCache");
1306
+ }, "clearCache"),
1307
+ getCacheStats: /* @__PURE__ */ __name(() => ({
1308
+ blobUrls: get().blobUrls.size,
1309
+ dimensions: get().imageDimensions.size,
1310
+ audioPositions: get().audioPlaybackPositions.size,
1311
+ videoPositions: get().videoPlaybackPositions.size
1312
+ }), "getCacheStats")
1313
+ }),
1314
+ {
1315
+ name: "media-cache-storage",
1316
+ // Only persist playback positions, poster URLs, and player settings
1317
+ partialize: /* @__PURE__ */ __name((state) => ({
1318
+ audioPlaybackPositions: Array.from(
1319
+ state.audioPlaybackPositions.entries()
1320
+ ),
1321
+ videoPlaybackPositions: Array.from(
1322
+ state.videoPlaybackPositions.entries()
1323
+ ),
1324
+ videoPosterUrls: Array.from(state.videoPosterUrls.entries()),
1325
+ videoPlayerSettings: state.videoPlayerSettings
1326
+ }), "partialize"),
1327
+ // Rehydrate Maps from arrays
1328
+ onRehydrateStorage: /* @__PURE__ */ __name(() => (state) => {
1329
+ if (state) {
1330
+ const persistedAudio = state.audioPlaybackPositions;
1331
+ const persistedVideo = state.videoPlaybackPositions;
1332
+ const persistedPosters = state.videoPosterUrls;
1333
+ const persistedSettings = state.videoPlayerSettings;
1334
+ state.audioPlaybackPositions = new Map(
1335
+ Array.isArray(persistedAudio) ? persistedAudio : []
1336
+ );
1337
+ state.videoPlaybackPositions = new Map(
1338
+ Array.isArray(persistedVideo) ? persistedVideo : []
1339
+ );
1340
+ state.videoPosterUrls = new Map(
1341
+ Array.isArray(persistedPosters) ? persistedPosters : []
1342
+ );
1343
+ state.videoPlayerSettings = {
1344
+ ...DEFAULT_VIDEO_SETTINGS,
1345
+ ...persistedSettings && typeof persistedSettings === "object" ? persistedSettings : {}
1346
+ };
1347
+ }
1348
+ }, "onRehydrateStorage")
1349
+ }
1350
+ ),
1351
+ { name: "MediaCacheStore" }
1352
+ )
1353
+ );
1354
+ var useImageCache = /* @__PURE__ */ __name(() => useMediaCacheStore(
1355
+ useShallow((state) => ({
1356
+ getOrCreateBlobUrl: state.getOrCreateBlobUrl,
1357
+ releaseBlobUrl: state.releaseBlobUrl,
1358
+ hasBlobUrl: state.hasBlobUrl,
1359
+ cacheDimensions: state.cacheDimensions,
1360
+ getDimensions: state.getDimensions
1361
+ }))
1362
+ ), "useImageCache");
1363
+ var useAudioCache = /* @__PURE__ */ __name(() => useMediaCacheStore(
1364
+ useShallow((state) => ({
1365
+ getOrCreateBlobUrl: state.getOrCreateBlobUrl,
1366
+ releaseBlobUrl: state.releaseBlobUrl,
1367
+ hasBlobUrl: state.hasBlobUrl,
1368
+ saveAudioPosition: state.saveAudioPosition,
1369
+ getAudioPosition: state.getAudioPosition,
1370
+ getEffectConfig: state.getEffectConfig,
1371
+ cacheEffectConfig: state.cacheEffectConfig
1372
+ }))
1373
+ ), "useAudioCache");
1374
+ var useVideoCache = /* @__PURE__ */ __name(() => useMediaCacheStore(
1375
+ useShallow((state) => ({
1376
+ getOrCreateBlobUrl: state.getOrCreateBlobUrl,
1377
+ releaseBlobUrl: state.releaseBlobUrl,
1378
+ hasBlobUrl: state.hasBlobUrl,
1379
+ getOrCreateStreamUrl: state.getOrCreateStreamUrl,
1380
+ getPosterUrl: state.getPosterUrl,
1381
+ cachePosterUrl: state.cachePosterUrl,
1382
+ saveVideoPosition: state.saveVideoPosition,
1383
+ getVideoPosition: state.getVideoPosition,
1384
+ cacheVideoMetadata: state.cacheVideoMetadata,
1385
+ getVideoMetadata: state.getVideoMetadata,
1386
+ invalidateSession: state.invalidateSession,
1387
+ getVideoPlayerSettings: state.getVideoPlayerSettings,
1388
+ saveVideoPlayerSettings: state.saveVideoPlayerSettings
1389
+ }))
1390
+ ), "useVideoCache");
1391
+ var useVideoPlayerSettings = /* @__PURE__ */ __name(() => useMediaCacheStore(
1392
+ useShallow((state) => ({
1393
+ settings: state.videoPlayerSettings,
1394
+ saveSettings: state.saveVideoPlayerSettings
1395
+ }))
1396
+ ), "useVideoPlayerSettings");
1397
+ function useBlobUrlCleanup(key) {
1398
+ const releaseBlobUrl = useMediaCacheStore((s) => s.releaseBlobUrl);
1399
+ if (typeof window !== "undefined") {
1400
+ const { useEffect: useEffect13 } = __require("react");
1401
+ useEffect13(() => {
1402
+ return () => {
1403
+ if (key) {
1404
+ releaseBlobUrl(key);
1405
+ }
1406
+ };
1407
+ }, [key, releaseBlobUrl]);
1408
+ }
1409
+ }
1410
+ __name(useBlobUrlCleanup, "useBlobUrlCleanup");
1411
+ function generateContentKey(content) {
1412
+ const arr = new Uint8Array(content);
1413
+ const len = arr.length;
1414
+ const start = arr.slice(0, Math.min(1024, len));
1415
+ const end = arr.slice(Math.max(0, len - 1024));
1416
+ let hash = len;
1417
+ for (let i = 0; i < start.length; i++) {
1418
+ hash = (hash << 5) - hash + start[i] | 0;
1419
+ }
1420
+ for (let i = 0; i < end.length; i++) {
1421
+ hash = (hash << 5) - hash + end[i] | 0;
1422
+ }
1423
+ return `blob-${len}-${hash.toString(16)}`;
1424
+ }
1425
+ __name(generateContentKey, "generateContentKey");
1426
+ var videoDebug = createMediaLogger("VideoPlayer");
1427
+ function getVidstackSrc(source) {
1428
+ switch (source.type) {
1429
+ case "youtube":
1430
+ return `youtube/${source.id}`;
1431
+ case "vimeo":
1432
+ return `vimeo/${source.id}`;
1433
+ case "hls":
1434
+ return { src: source.url, type: "application/x-mpegurl" };
1435
+ case "dash":
1436
+ return { src: source.url, type: "application/dash+xml" };
1437
+ case "url":
1438
+ return source.url;
1439
+ default:
1440
+ return "";
1441
+ }
1442
+ }
1443
+ __name(getVidstackSrc, "getVidstackSrc");
1444
+ function DefaultErrorFallback({ error }) {
1445
+ return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-4 text-white bg-black", children: [
1446
+ /* @__PURE__ */ jsx(
1447
+ "svg",
1448
+ {
1449
+ className: "w-16 h-16 text-muted-foreground",
1450
+ fill: "none",
1451
+ stroke: "currentColor",
1452
+ viewBox: "0 0 24 24",
1453
+ children: /* @__PURE__ */ jsx(
1454
+ "path",
1455
+ {
1456
+ strokeLinecap: "round",
1457
+ strokeLinejoin: "round",
1458
+ strokeWidth: 2,
1459
+ d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
1460
+ }
1461
+ )
1462
+ }
1463
+ ),
1464
+ /* @__PURE__ */ jsx("p", { className: "text-lg", children: error || "Video cannot be played" })
1465
+ ] });
1466
+ }
1467
+ __name(DefaultErrorFallback, "DefaultErrorFallback");
1468
+ var VidstackProvider = forwardRef(
1469
+ ({
1470
+ source,
1471
+ aspectRatio = 16 / 9,
1472
+ autoPlay = false,
1473
+ muted = false,
1474
+ loop = false,
1475
+ playsInline = true,
1476
+ controls = true,
1477
+ className,
1478
+ showInfo = false,
1479
+ theme = "default",
1480
+ errorFallback,
1481
+ onPlay,
1482
+ onPause,
1483
+ onEnded,
1484
+ onError,
1485
+ onLoadStart,
1486
+ onCanPlay,
1487
+ onTimeUpdate
1488
+ }, ref) => {
1489
+ const playerRef = useRef(null);
1490
+ const [hasError, setHasError] = useState(false);
1491
+ const [errorMessage, setErrorMessage] = useState("Video cannot be played");
1492
+ const lastSavedTimeRef = useRef(0);
1493
+ const hasRestoredPositionRef = useRef(false);
1494
+ const {
1495
+ getPosterUrl,
1496
+ cachePosterUrl,
1497
+ saveVideoPosition,
1498
+ getVideoPosition
1499
+ } = useVideoCache();
1500
+ const sourceKey = useMemo(() => {
1501
+ switch (source.type) {
1502
+ case "youtube":
1503
+ return `youtube:${source.id}`;
1504
+ case "vimeo":
1505
+ return `vimeo:${source.id}`;
1506
+ case "hls":
1507
+ case "dash":
1508
+ case "url":
1509
+ return `url:${source.url}`;
1510
+ default:
1511
+ return null;
1512
+ }
1513
+ }, [source]);
1514
+ const posterUrl = useMemo(() => {
1515
+ if (source.poster) return source.poster;
1516
+ if (!source.title) return void 0;
1517
+ const cached = getPosterUrl(source.title);
1518
+ if (cached) return cached;
1519
+ const url = generateOgImageUrl({ title: source.title });
1520
+ cachePosterUrl(source.title, url);
1521
+ return url;
1522
+ }, [source.poster, source.title, getPosterUrl, cachePosterUrl]);
1523
+ const vidstackSrc = useMemo(() => getVidstackSrc(source), [source]);
1524
+ useEffect(() => {
1525
+ const srcString = typeof vidstackSrc === "string" ? vidstackSrc : vidstackSrc.src;
1526
+ videoDebug.load(srcString, source.type);
1527
+ }, [vidstackSrc, source.type]);
1528
+ const retry = useCallback(() => {
1529
+ setHasError(false);
1530
+ setErrorMessage("Video cannot be played");
1531
+ const player = playerRef.current;
1532
+ if (player) {
1533
+ player.currentTime = 0;
1534
+ player.play();
1535
+ }
1536
+ }, []);
1537
+ useImperativeHandle(
1538
+ ref,
1539
+ () => {
1540
+ const player = playerRef.current;
1541
+ return {
1542
+ play: /* @__PURE__ */ __name(() => player?.play(), "play"),
1543
+ pause: /* @__PURE__ */ __name(() => player?.pause(), "pause"),
1544
+ togglePlay: /* @__PURE__ */ __name(() => {
1545
+ if (player) {
1546
+ player.paused ? player.play() : player.pause();
1547
+ }
1548
+ }, "togglePlay"),
1549
+ seekTo: /* @__PURE__ */ __name((time) => {
1550
+ if (player) player.currentTime = time;
1551
+ }, "seekTo"),
1552
+ setVolume: /* @__PURE__ */ __name((volume) => {
1553
+ if (player) player.volume = Math.max(0, Math.min(1, volume));
1554
+ }, "setVolume"),
1555
+ toggleMute: /* @__PURE__ */ __name(() => {
1556
+ if (player) player.muted = !player.muted;
1557
+ }, "toggleMute"),
1558
+ enterFullscreen: /* @__PURE__ */ __name(() => player?.enterFullscreen(), "enterFullscreen"),
1559
+ exitFullscreen: /* @__PURE__ */ __name(() => player?.exitFullscreen(), "exitFullscreen"),
1560
+ get currentTime() {
1561
+ return player?.currentTime ?? 0;
1562
+ },
1563
+ get duration() {
1564
+ return player?.duration ?? 0;
1565
+ },
1566
+ get paused() {
1567
+ return player?.paused ?? true;
1568
+ }
1569
+ };
1570
+ },
1571
+ []
1572
+ );
1573
+ const handlePlay = /* @__PURE__ */ __name(() => onPlay?.(), "handlePlay");
1574
+ const handlePause = useCallback(() => {
1575
+ const player = playerRef.current;
1576
+ if (sourceKey && player && player.currentTime > 0) {
1577
+ saveVideoPosition(sourceKey, player.currentTime);
1578
+ lastSavedTimeRef.current = player.currentTime;
1579
+ }
1580
+ onPause?.();
1581
+ }, [sourceKey, saveVideoPosition, onPause]);
1582
+ const handleEnded = /* @__PURE__ */ __name(() => onEnded?.(), "handleEnded");
1583
+ const handleError = /* @__PURE__ */ __name((detail) => {
1584
+ const error = detail;
1585
+ const msg = error?.message || "Video playback error";
1586
+ videoDebug.error("Vidstack error", { message: msg });
1587
+ setHasError(true);
1588
+ setErrorMessage(msg);
1589
+ onError?.(msg);
1590
+ }, "handleError");
1591
+ const handleLoadStart = /* @__PURE__ */ __name(() => onLoadStart?.(), "handleLoadStart");
1592
+ const handleCanPlay = useCallback(() => {
1593
+ const player = playerRef.current;
1594
+ if (player) {
1595
+ videoDebug.state("canplay", { duration: player.duration });
1596
+ const mediaEl = player.provider?.media;
1597
+ if (mediaEl?.buffered) {
1598
+ videoDebug.buffer(mediaEl.buffered, player.duration);
1599
+ }
1600
+ }
1601
+ setHasError(false);
1602
+ if (sourceKey && player && !hasRestoredPositionRef.current) {
1603
+ const cachedPosition = getVideoPosition(sourceKey);
1604
+ if (cachedPosition && cachedPosition > 0) {
1605
+ const duration = player.duration;
1606
+ if (cachedPosition < duration - 1) {
1607
+ videoDebug.debug(`Restoring position: ${cachedPosition}s`);
1608
+ player.currentTime = cachedPosition;
1609
+ }
1610
+ }
1611
+ hasRestoredPositionRef.current = true;
1612
+ }
1613
+ onCanPlay?.();
1614
+ }, [sourceKey, getVideoPosition, onCanPlay]);
1615
+ const handleTimeUpdate = useCallback(() => {
1616
+ const player = playerRef.current;
1617
+ if (!player) return;
1618
+ if (sourceKey && player.currentTime > 0) {
1619
+ const timeSinceLastSave = player.currentTime - lastSavedTimeRef.current;
1620
+ if (timeSinceLastSave >= 5 || timeSinceLastSave < 0) {
1621
+ saveVideoPosition(sourceKey, player.currentTime);
1622
+ lastSavedTimeRef.current = player.currentTime;
1623
+ }
1624
+ }
1625
+ onTimeUpdate?.(player.currentTime, player.duration);
1626
+ }, [sourceKey, saveVideoPosition, onTimeUpdate]);
1627
+ useEffect(() => {
1628
+ hasRestoredPositionRef.current = false;
1629
+ lastSavedTimeRef.current = 0;
1630
+ }, [sourceKey]);
1631
+ useEffect(() => {
1632
+ const player = playerRef.current;
1633
+ if (!player) return;
1634
+ const handleSeeking = /* @__PURE__ */ __name(() => {
1635
+ videoDebug.event("seeking", { currentTime: player.currentTime });
1636
+ }, "handleSeeking");
1637
+ const handleSeeked = /* @__PURE__ */ __name(() => {
1638
+ videoDebug.event("seeked", { currentTime: player.currentTime });
1639
+ const mediaEl = player.provider?.media;
1640
+ if (mediaEl?.buffered) {
1641
+ videoDebug.buffer(mediaEl.buffered, player.duration);
1642
+ }
1643
+ }, "handleSeeked");
1644
+ const handleWaiting = /* @__PURE__ */ __name(() => {
1645
+ videoDebug.warn("WAITING - buffering...");
1646
+ const mediaEl = player.provider?.media;
1647
+ if (mediaEl?.buffered) {
1648
+ videoDebug.buffer(mediaEl.buffered, player.duration);
1649
+ }
1650
+ }, "handleWaiting");
1651
+ const handleStalled = /* @__PURE__ */ __name(() => {
1652
+ videoDebug.warn("STALLED - network issue");
1653
+ const mediaEl = player.provider?.media;
1654
+ if (mediaEl?.buffered) {
1655
+ videoDebug.buffer(mediaEl.buffered, player.duration);
1656
+ }
1657
+ }, "handleStalled");
1658
+ player.addEventListener("seeking", handleSeeking);
1659
+ player.addEventListener("seeked", handleSeeked);
1660
+ player.addEventListener("waiting", handleWaiting);
1661
+ player.addEventListener("stalled", handleStalled);
1662
+ return () => {
1663
+ player.removeEventListener("seeking", handleSeeking);
1664
+ player.removeEventListener("seeked", handleSeeked);
1665
+ player.removeEventListener("waiting", handleWaiting);
1666
+ player.removeEventListener("stalled", handleStalled);
1667
+ };
1668
+ }, [vidstackSrc]);
1669
+ const isFillMode = aspectRatio === "fill";
1670
+ const computedAspectRatio = aspectRatio === "auto" || aspectRatio === "fill" ? void 0 : aspectRatio;
1671
+ const renderErrorFallback = /* @__PURE__ */ __name(() => {
1672
+ const fallbackProps = { error: errorMessage, retry };
1673
+ if (typeof errorFallback === "function") {
1674
+ return errorFallback(fallbackProps);
1675
+ }
1676
+ if (errorFallback) {
1677
+ return errorFallback;
1678
+ }
1679
+ return /* @__PURE__ */ jsx(DefaultErrorFallback, { ...fallbackProps });
1680
+ }, "renderErrorFallback");
1681
+ const containerStyles = isFillMode ? { width: "100%", height: "100%" } : { aspectRatio: computedAspectRatio };
1682
+ return /* @__PURE__ */ jsxs("div", { className: cn(isFillMode ? "w-full h-full" : "w-full", className), children: [
1683
+ /* @__PURE__ */ jsx(
1684
+ "div",
1685
+ {
1686
+ className: cn(
1687
+ "relative w-full rounded-sm bg-black overflow-hidden",
1688
+ isFillMode && "h-full",
1689
+ theme === "minimal" && "rounded-none",
1690
+ theme === "modern" && "rounded-xl shadow-2xl"
1691
+ ),
1692
+ style: containerStyles,
1693
+ children: hasError ? renderErrorFallback() : /* @__PURE__ */ jsxs(
1694
+ MediaPlayer,
1695
+ {
1696
+ ref: playerRef,
1697
+ title: source.title || "Video",
1698
+ src: vidstackSrc,
1699
+ autoPlay,
1700
+ muted,
1701
+ loop,
1702
+ playsInline,
1703
+ onPlay: handlePlay,
1704
+ onPause: handlePause,
1705
+ onEnded: handleEnded,
1706
+ onError: handleError,
1707
+ onLoadStart: handleLoadStart,
1708
+ onCanPlay: handleCanPlay,
1709
+ onTimeUpdate: handleTimeUpdate,
1710
+ className: "w-full h-full",
1711
+ children: [
1712
+ /* @__PURE__ */ jsx(MediaProvider, {}),
1713
+ posterUrl && /* @__PURE__ */ jsx(
1714
+ Poster,
1715
+ {
1716
+ className: "vds-poster",
1717
+ src: posterUrl,
1718
+ alt: source.title || "Video poster",
1719
+ style: { objectFit: "cover" }
1720
+ }
1721
+ ),
1722
+ controls && /* @__PURE__ */ jsx(DefaultVideoLayout, { icons: defaultLayoutIcons, thumbnails: posterUrl })
1723
+ ]
1724
+ }
1725
+ )
1726
+ }
1727
+ ),
1728
+ showInfo && source.title && /* @__PURE__ */ jsx("div", { className: "mt-4 space-y-2", children: /* @__PURE__ */ jsx("h3", { className: "text-xl font-semibold text-foreground", children: source.title }) })
1729
+ ] });
1730
+ }
1731
+ );
1732
+ VidstackProvider.displayName = "VidstackProvider";
1733
+ function useVideoPlayerSettings2() {
1734
+ const { settings, saveSettings } = useVideoPlayerSettings();
1735
+ const updateVolume = useCallback(
1736
+ (volume) => {
1737
+ saveSettings({ volume: Math.max(0, Math.min(1, volume)) });
1738
+ },
1739
+ [saveSettings]
1740
+ );
1741
+ const updateLoop = useCallback(
1742
+ (isLooping) => {
1743
+ saveSettings({ isLooping });
1744
+ },
1745
+ [saveSettings]
1746
+ );
1747
+ const updateSettings = useCallback(
1748
+ (newSettings) => {
1749
+ saveSettings(newSettings);
1750
+ },
1751
+ [saveSettings]
1752
+ );
1753
+ return {
1754
+ settings,
1755
+ updateVolume,
1756
+ updateLoop,
1757
+ updateSettings
1758
+ };
1759
+ }
1760
+ __name(useVideoPlayerSettings2, "useVideoPlayerSettings");
1761
+ function getVideoUrl(source) {
1762
+ switch (source.type) {
1763
+ case "url":
1764
+ return source.url;
1765
+ case "data-url":
1766
+ return source.data;
1767
+ default:
1768
+ return "";
1769
+ }
1770
+ }
1771
+ __name(getVideoUrl, "getVideoUrl");
1772
+ var NativeProvider = forwardRef(
1773
+ ({
1774
+ source,
1775
+ aspectRatio = 16 / 9,
1776
+ autoPlay = true,
1777
+ muted = true,
1778
+ loop = true,
1779
+ playsInline = true,
1780
+ preload = "auto",
1781
+ controls = false,
1782
+ disableContextMenu = true,
1783
+ showPreloader = true,
1784
+ preloaderTimeout = 5e3,
1785
+ className,
1786
+ videoClassName,
1787
+ onPlay,
1788
+ onPause,
1789
+ onEnded,
1790
+ onError,
1791
+ onLoadStart,
1792
+ onCanPlay,
1793
+ onTimeUpdate
1794
+ }, ref) => {
1795
+ const [isLoading, setIsLoading] = useState(showPreloader);
1796
+ const videoRef = useRef(null);
1797
+ const { settings: savedSettings, updateVolume } = useVideoPlayerSettings2();
1798
+ const videoUrl = getVideoUrl(source);
1799
+ useEffect(() => {
1800
+ videoDebug.load(videoUrl, source.type);
1801
+ }, [videoUrl, source.type]);
1802
+ useImperativeHandle(
1803
+ ref,
1804
+ () => ({
1805
+ play: /* @__PURE__ */ __name(() => videoRef.current?.play(), "play"),
1806
+ pause: /* @__PURE__ */ __name(() => videoRef.current?.pause(), "pause"),
1807
+ togglePlay: /* @__PURE__ */ __name(() => {
1808
+ const video = videoRef.current;
1809
+ if (video) {
1810
+ video.paused ? video.play() : video.pause();
1811
+ }
1812
+ }, "togglePlay"),
1813
+ seekTo: /* @__PURE__ */ __name((time) => {
1814
+ if (videoRef.current) videoRef.current.currentTime = time;
1815
+ }, "seekTo"),
1816
+ setVolume: /* @__PURE__ */ __name((volume) => {
1817
+ if (videoRef.current) videoRef.current.volume = Math.max(0, Math.min(1, volume));
1818
+ }, "setVolume"),
1819
+ toggleMute: /* @__PURE__ */ __name(() => {
1820
+ if (videoRef.current) videoRef.current.muted = !videoRef.current.muted;
1821
+ }, "toggleMute"),
1822
+ enterFullscreen: /* @__PURE__ */ __name(() => videoRef.current?.requestFullscreen(), "enterFullscreen"),
1823
+ exitFullscreen: /* @__PURE__ */ __name(() => document.exitFullscreen(), "exitFullscreen"),
1824
+ get currentTime() {
1825
+ return videoRef.current?.currentTime ?? 0;
1826
+ },
1827
+ get duration() {
1828
+ return videoRef.current?.duration ?? 0;
1829
+ },
1830
+ get paused() {
1831
+ return videoRef.current?.paused ?? true;
1832
+ }
1833
+ }),
1834
+ []
1835
+ );
1836
+ useEffect(() => {
1837
+ if (!showPreloader) return;
1838
+ const video = videoRef.current;
1839
+ if (!video) return;
1840
+ if (video.readyState >= 3) {
1841
+ setIsLoading(false);
1842
+ return;
1843
+ }
1844
+ const hideLoader = /* @__PURE__ */ __name(() => setIsLoading(false), "hideLoader");
1845
+ video.addEventListener("canplay", hideLoader);
1846
+ video.addEventListener("loadeddata", hideLoader);
1847
+ video.addEventListener("playing", hideLoader);
1848
+ const timeout = setTimeout(hideLoader, preloaderTimeout);
1849
+ return () => {
1850
+ video.removeEventListener("canplay", hideLoader);
1851
+ video.removeEventListener("loadeddata", hideLoader);
1852
+ video.removeEventListener("playing", hideLoader);
1853
+ clearTimeout(timeout);
1854
+ };
1855
+ }, [showPreloader, preloaderTimeout]);
1856
+ useEffect(() => {
1857
+ const video = videoRef.current;
1858
+ if (!video) return;
1859
+ const handleLoadedMetadata = /* @__PURE__ */ __name(() => {
1860
+ videoDebug.state("loadedmetadata", { duration: video.duration });
1861
+ }, "handleLoadedMetadata");
1862
+ const handleCanPlayDebug = /* @__PURE__ */ __name(() => {
1863
+ videoDebug.state("canplay", { duration: video.duration, buffered: video.buffered.length });
1864
+ videoDebug.buffer(video.buffered, video.duration);
1865
+ video.volume = savedSettings.volume;
1866
+ }, "handleCanPlayDebug");
1867
+ const handleSeeking = /* @__PURE__ */ __name(() => {
1868
+ videoDebug.event("seeking", { currentTime: video.currentTime });
1869
+ }, "handleSeeking");
1870
+ const handleSeeked = /* @__PURE__ */ __name(() => {
1871
+ videoDebug.event("seeked", { currentTime: video.currentTime });
1872
+ videoDebug.buffer(video.buffered, video.duration);
1873
+ }, "handleSeeked");
1874
+ const handleWaiting = /* @__PURE__ */ __name(() => {
1875
+ videoDebug.warn("WAITING - buffering...");
1876
+ videoDebug.buffer(video.buffered, video.duration);
1877
+ }, "handleWaiting");
1878
+ const handleStalled = /* @__PURE__ */ __name(() => {
1879
+ videoDebug.warn("STALLED - network issue");
1880
+ videoDebug.buffer(video.buffered, video.duration);
1881
+ }, "handleStalled");
1882
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
1883
+ video.addEventListener("canplay", handleCanPlayDebug);
1884
+ video.addEventListener("seeking", handleSeeking);
1885
+ video.addEventListener("seeked", handleSeeked);
1886
+ video.addEventListener("waiting", handleWaiting);
1887
+ video.addEventListener("stalled", handleStalled);
1888
+ return () => {
1889
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
1890
+ video.removeEventListener("canplay", handleCanPlayDebug);
1891
+ video.removeEventListener("seeking", handleSeeking);
1892
+ video.removeEventListener("seeked", handleSeeked);
1893
+ video.removeEventListener("waiting", handleWaiting);
1894
+ video.removeEventListener("stalled", handleStalled);
1895
+ };
1896
+ }, [savedSettings.volume]);
1897
+ useEffect(() => {
1898
+ const video = videoRef.current;
1899
+ if (!video) return;
1900
+ const handleVolumeChange = /* @__PURE__ */ __name(() => {
1901
+ updateVolume(video.volume);
1902
+ }, "handleVolumeChange");
1903
+ video.addEventListener("volumechange", handleVolumeChange);
1904
+ return () => video.removeEventListener("volumechange", handleVolumeChange);
1905
+ }, [updateVolume]);
1906
+ const handleContextMenu = /* @__PURE__ */ __name((e) => {
1907
+ if (disableContextMenu) {
1908
+ e.preventDefault();
1909
+ }
1910
+ }, "handleContextMenu");
1911
+ const handleError = /* @__PURE__ */ __name((e) => {
1912
+ const video = e.currentTarget;
1913
+ const errorMsg = video.error?.message || "Video playback error";
1914
+ videoDebug.error("Video error", { code: video.error?.code, message: errorMsg });
1915
+ setIsLoading(false);
1916
+ onError?.(errorMsg);
1917
+ }, "handleError");
1918
+ const handleTimeUpdate = /* @__PURE__ */ __name(() => {
1919
+ const video = videoRef.current;
1920
+ if (video && onTimeUpdate) {
1921
+ onTimeUpdate(video.currentTime, video.duration);
1922
+ }
1923
+ }, "handleTimeUpdate");
1924
+ const isFillMode = aspectRatio === "fill";
1925
+ const computedAspectRatio = aspectRatio === "auto" || aspectRatio === "fill" ? void 0 : aspectRatio;
1926
+ const videoContent = /* @__PURE__ */ jsxs(Fragment, { children: [
1927
+ showPreloader && isLoading && /* @__PURE__ */ jsx(
1928
+ "div",
1929
+ {
1930
+ className: cn(
1931
+ "absolute inset-0 flex items-center justify-center bg-muted/30 backdrop-blur-sm z-10"
1932
+ ),
1933
+ children: /* @__PURE__ */ jsx(Preloader, { size: "lg", spinnerClassName: "text-white" })
1934
+ }
1935
+ ),
1936
+ /* @__PURE__ */ jsx(
1937
+ "video",
1938
+ {
1939
+ ref: videoRef,
1940
+ className: cn("w-full h-full object-cover", videoClassName),
1941
+ src: videoUrl,
1942
+ autoPlay,
1943
+ muted,
1944
+ loop,
1945
+ playsInline,
1946
+ preload,
1947
+ controls,
1948
+ poster: source.poster,
1949
+ onContextMenu: handleContextMenu,
1950
+ onLoadStart,
1951
+ onCanPlay,
1952
+ onPlay,
1953
+ onPause,
1954
+ onPlaying: () => setIsLoading(false),
1955
+ onEnded,
1956
+ onError: handleError,
1957
+ onTimeUpdate: handleTimeUpdate
1958
+ }
1959
+ )
1960
+ ] });
1961
+ if (isFillMode) {
1962
+ return /* @__PURE__ */ jsx("div", { className: cn("relative w-full h-full overflow-hidden", className), children: videoContent });
1963
+ }
1964
+ return /* @__PURE__ */ jsx("div", { className: cn("relative overflow-hidden", className), children: /* @__PURE__ */ jsx(AspectRatio, { ratio: computedAspectRatio, children: videoContent }) });
1965
+ }
1966
+ );
1967
+ NativeProvider.displayName = "NativeProvider";
1968
+ function DefaultErrorFallback2({ error }) {
1969
+ return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-4 text-white", children: [
1970
+ /* @__PURE__ */ jsx(
1971
+ "svg",
1972
+ {
1973
+ className: "w-16 h-16 text-muted-foreground",
1974
+ fill: "none",
1975
+ stroke: "currentColor",
1976
+ viewBox: "0 0 24 24",
1977
+ children: /* @__PURE__ */ jsx(
1978
+ "path",
1979
+ {
1980
+ strokeLinecap: "round",
1981
+ strokeLinejoin: "round",
1982
+ strokeWidth: 2,
1983
+ d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
1984
+ }
1985
+ )
1986
+ }
1987
+ ),
1988
+ /* @__PURE__ */ jsx("p", { className: "text-lg", children: error || "Video cannot be previewed" })
1989
+ ] });
1990
+ }
1991
+ __name(DefaultErrorFallback2, "DefaultErrorFallback");
1992
+ var StreamProvider = forwardRef(
1993
+ ({
1994
+ source,
1995
+ aspectRatio = 16 / 9,
1996
+ autoPlay = false,
1997
+ muted = false,
1998
+ loop = false,
1999
+ playsInline = true,
2000
+ preload = "metadata",
2001
+ controls = true,
2002
+ disableContextMenu = false,
2003
+ showPreloader = true,
2004
+ preloaderTimeout = 1e4,
2005
+ className,
2006
+ videoClassName,
2007
+ errorFallback,
2008
+ onPlay,
2009
+ onPause,
2010
+ onEnded,
2011
+ onError,
2012
+ onLoadStart,
2013
+ onCanPlay,
2014
+ onTimeUpdate,
2015
+ onBufferProgress
2016
+ }, ref) => {
2017
+ const [videoUrl, setVideoUrl] = useState(null);
2018
+ const [isLoading, setIsLoading] = useState(true);
2019
+ const [hasError, setHasError] = useState(false);
2020
+ const [errorMessage, setErrorMessage] = useState("Video cannot be previewed");
2021
+ const videoRef = useRef(null);
2022
+ const contentKeyRef = useRef(null);
2023
+ const lastSavedTimeRef = useRef(0);
2024
+ const getOrCreateBlobUrl = useMediaCacheStore.getState().getOrCreateBlobUrl;
2025
+ const releaseBlobUrl = useMediaCacheStore.getState().releaseBlobUrl;
2026
+ const getOrCreateStreamUrl = useMediaCacheStore.getState().getOrCreateStreamUrl;
2027
+ const saveVideoPosition = useMediaCacheStore.getState().saveVideoPosition;
2028
+ const getVideoPosition = useMediaCacheStore.getState().getVideoPosition;
2029
+ const { settings: savedSettings, updateVolume } = useVideoPlayerSettings2();
2030
+ const retry = useCallback(() => {
2031
+ setHasError(false);
2032
+ setIsLoading(true);
2033
+ if (source.type === "stream") {
2034
+ const streamSource = source;
2035
+ const freshUrl = streamSource.getStreamUrl(streamSource.sessionId, streamSource.path);
2036
+ setVideoUrl(freshUrl);
2037
+ return;
2038
+ }
2039
+ const video = videoRef.current;
2040
+ if (video && videoUrl) {
2041
+ video.load();
2042
+ }
2043
+ }, [source, videoUrl]);
2044
+ useImperativeHandle(
2045
+ ref,
2046
+ () => ({
2047
+ play: /* @__PURE__ */ __name(() => videoRef.current?.play(), "play"),
2048
+ pause: /* @__PURE__ */ __name(() => videoRef.current?.pause(), "pause"),
2049
+ togglePlay: /* @__PURE__ */ __name(() => {
2050
+ const video = videoRef.current;
2051
+ if (video) {
2052
+ video.paused ? video.play() : video.pause();
2053
+ }
2054
+ }, "togglePlay"),
2055
+ seekTo: /* @__PURE__ */ __name((time) => {
2056
+ if (videoRef.current) videoRef.current.currentTime = time;
2057
+ }, "seekTo"),
2058
+ setVolume: /* @__PURE__ */ __name((volume) => {
2059
+ if (videoRef.current) videoRef.current.volume = Math.max(0, Math.min(1, volume));
2060
+ }, "setVolume"),
2061
+ toggleMute: /* @__PURE__ */ __name(() => {
2062
+ if (videoRef.current) videoRef.current.muted = !videoRef.current.muted;
2063
+ }, "toggleMute"),
2064
+ enterFullscreen: /* @__PURE__ */ __name(() => videoRef.current?.requestFullscreen(), "enterFullscreen"),
2065
+ exitFullscreen: /* @__PURE__ */ __name(() => document.exitFullscreen(), "exitFullscreen"),
2066
+ get currentTime() {
2067
+ return videoRef.current?.currentTime ?? 0;
2068
+ },
2069
+ get duration() {
2070
+ return videoRef.current?.duration ?? 0;
2071
+ },
2072
+ get paused() {
2073
+ return videoRef.current?.paused ?? true;
2074
+ }
2075
+ }),
2076
+ []
2077
+ );
2078
+ const isMountedRef = useRef(true);
2079
+ useEffect(() => {
2080
+ isMountedRef.current = true;
2081
+ return () => {
2082
+ isMountedRef.current = false;
2083
+ if (contentKeyRef.current) {
2084
+ useMediaCacheStore.getState().releaseBlobUrl(contentKeyRef.current);
2085
+ contentKeyRef.current = null;
2086
+ }
2087
+ };
2088
+ }, []);
2089
+ useEffect(() => {
2090
+ if (contentKeyRef.current) {
2091
+ const newKey = source.type === "blob" ? generateContentKey(source.data) : null;
2092
+ if (newKey !== contentKeyRef.current) {
2093
+ releaseBlobUrl(contentKeyRef.current);
2094
+ contentKeyRef.current = null;
2095
+ }
2096
+ }
2097
+ setHasError(false);
2098
+ setIsLoading(true);
2099
+ switch (source.type) {
2100
+ case "stream": {
2101
+ const streamSource = source;
2102
+ const url = getOrCreateStreamUrl(
2103
+ streamSource.sessionId,
2104
+ streamSource.path,
2105
+ streamSource.getStreamUrl
2106
+ );
2107
+ videoDebug.load(url, "stream");
2108
+ setVideoUrl(url);
2109
+ break;
2110
+ }
2111
+ case "blob": {
2112
+ const blobSource = source;
2113
+ const contentKey = generateContentKey(blobSource.data);
2114
+ contentKeyRef.current = contentKey;
2115
+ const url = getOrCreateBlobUrl(
2116
+ contentKey,
2117
+ blobSource.data,
2118
+ blobSource.mimeType || "video/mp4"
2119
+ );
2120
+ videoDebug.load(url, "blob");
2121
+ setVideoUrl(url);
2122
+ break;
2123
+ }
2124
+ case "data-url": {
2125
+ const dataUrlSource = source;
2126
+ videoDebug.load(dataUrlSource.data.slice(0, 50) + "...", "data-url");
2127
+ setVideoUrl(dataUrlSource.data);
2128
+ break;
2129
+ }
2130
+ default:
2131
+ videoDebug.error("Invalid video source type", { type: source.type });
2132
+ setVideoUrl(null);
2133
+ setHasError(true);
2134
+ setErrorMessage("Invalid video source");
2135
+ }
2136
+ }, [source]);
2137
+ const getSourceKey = useCallback(() => {
2138
+ switch (source.type) {
2139
+ case "stream":
2140
+ return `stream:${source.sessionId}:${source.path}`;
2141
+ case "blob":
2142
+ return contentKeyRef.current ? `blob:${contentKeyRef.current}` : null;
2143
+ case "data-url":
2144
+ return `data:${source.data.slice(0, 50)}`;
2145
+ default:
2146
+ return null;
2147
+ }
2148
+ }, [source]);
2149
+ const handleCanPlay = useCallback(() => {
2150
+ const video = videoRef.current;
2151
+ if (video) {
2152
+ videoDebug.state("canplay", { duration: video.duration, buffered: video.buffered.length });
2153
+ videoDebug.buffer(video.buffered, video.duration);
2154
+ video.volume = savedSettings.volume;
2155
+ }
2156
+ setIsLoading(false);
2157
+ const sourceKey = getSourceKey();
2158
+ if (sourceKey && video) {
2159
+ const cachedPosition = getVideoPosition(sourceKey);
2160
+ if (cachedPosition && cachedPosition > 0) {
2161
+ const duration = video.duration;
2162
+ if (cachedPosition < duration - 1) {
2163
+ videoDebug.debug(`Restoring position: ${cachedPosition}s`);
2164
+ video.currentTime = cachedPosition;
2165
+ }
2166
+ }
2167
+ }
2168
+ onCanPlay?.();
2169
+ }, [getSourceKey, onCanPlay, savedSettings.volume]);
2170
+ const handleTimeUpdate = useCallback(() => {
2171
+ const video = videoRef.current;
2172
+ if (!video) return;
2173
+ const sourceKey = getSourceKey();
2174
+ if (sourceKey && video.currentTime > 0) {
2175
+ const timeSinceLastSave = video.currentTime - lastSavedTimeRef.current;
2176
+ if (timeSinceLastSave >= 5 || timeSinceLastSave < 0) {
2177
+ saveVideoPosition(sourceKey, video.currentTime);
2178
+ lastSavedTimeRef.current = video.currentTime;
2179
+ }
2180
+ }
2181
+ onTimeUpdate?.(video.currentTime, video.duration);
2182
+ }, [getSourceKey, onTimeUpdate]);
2183
+ const handlePause = useCallback(() => {
2184
+ const video = videoRef.current;
2185
+ const sourceKey = getSourceKey();
2186
+ if (sourceKey && video && video.currentTime > 0) {
2187
+ saveVideoPosition(sourceKey, video.currentTime);
2188
+ lastSavedTimeRef.current = video.currentTime;
2189
+ }
2190
+ onPause?.();
2191
+ }, [getSourceKey, onPause]);
2192
+ const handleProgress = useCallback(() => {
2193
+ const video = videoRef.current;
2194
+ if (!video || !onBufferProgress) return;
2195
+ if (video.buffered.length > 0) {
2196
+ const bufferedEnd = video.buffered.end(video.buffered.length - 1);
2197
+ const duration = video.duration;
2198
+ if (duration > 0 && !isNaN(bufferedEnd)) {
2199
+ onBufferProgress(bufferedEnd, duration);
2200
+ }
2201
+ }
2202
+ }, [onBufferProgress]);
2203
+ useEffect(() => {
2204
+ if (!showPreloader || !isLoading) return;
2205
+ const timeout = setTimeout(() => {
2206
+ setIsLoading(false);
2207
+ }, preloaderTimeout);
2208
+ return () => clearTimeout(timeout);
2209
+ }, [showPreloader, isLoading, preloaderTimeout]);
2210
+ const handleContextMenu = /* @__PURE__ */ __name((e) => {
2211
+ if (disableContextMenu) {
2212
+ e.preventDefault();
2213
+ }
2214
+ }, "handleContextMenu");
2215
+ const handleLoadedData = /* @__PURE__ */ __name(() => {
2216
+ setIsLoading(false);
2217
+ }, "handleLoadedData");
2218
+ const handleError = /* @__PURE__ */ __name(() => {
2219
+ const video = videoRef.current;
2220
+ if (video) {
2221
+ videoDebug.error("Video error", { code: video.error?.code, message: video.error?.message });
2222
+ }
2223
+ setIsLoading(false);
2224
+ setHasError(true);
2225
+ setErrorMessage("Failed to load video");
2226
+ onError?.("Video playback error");
2227
+ }, "handleError");
2228
+ useEffect(() => {
2229
+ const video = videoRef.current;
2230
+ if (!video) return;
2231
+ const handleLoadedMetadata = /* @__PURE__ */ __name(() => {
2232
+ videoDebug.state("loadedmetadata", { duration: video.duration });
2233
+ }, "handleLoadedMetadata");
2234
+ const handleSeeking = /* @__PURE__ */ __name(() => {
2235
+ videoDebug.event("seeking", { currentTime: video.currentTime });
2236
+ }, "handleSeeking");
2237
+ const handleSeeked = /* @__PURE__ */ __name(() => {
2238
+ videoDebug.event("seeked", { currentTime: video.currentTime });
2239
+ videoDebug.buffer(video.buffered, video.duration);
2240
+ }, "handleSeeked");
2241
+ const handleWaiting = /* @__PURE__ */ __name(() => {
2242
+ videoDebug.warn("WAITING - buffering...");
2243
+ videoDebug.buffer(video.buffered, video.duration);
2244
+ }, "handleWaiting");
2245
+ const handleStalled = /* @__PURE__ */ __name(() => {
2246
+ videoDebug.warn("STALLED - network issue");
2247
+ videoDebug.buffer(video.buffered, video.duration);
2248
+ }, "handleStalled");
2249
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
2250
+ video.addEventListener("seeking", handleSeeking);
2251
+ video.addEventListener("seeked", handleSeeked);
2252
+ video.addEventListener("waiting", handleWaiting);
2253
+ video.addEventListener("stalled", handleStalled);
2254
+ return () => {
2255
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
2256
+ video.removeEventListener("seeking", handleSeeking);
2257
+ video.removeEventListener("seeked", handleSeeked);
2258
+ video.removeEventListener("waiting", handleWaiting);
2259
+ video.removeEventListener("stalled", handleStalled);
2260
+ };
2261
+ }, [videoUrl]);
2262
+ useEffect(() => {
2263
+ const video = videoRef.current;
2264
+ if (!video) return;
2265
+ const handleVolumeChange = /* @__PURE__ */ __name(() => {
2266
+ updateVolume(video.volume);
2267
+ }, "handleVolumeChange");
2268
+ video.addEventListener("volumechange", handleVolumeChange);
2269
+ return () => video.removeEventListener("volumechange", handleVolumeChange);
2270
+ }, [videoUrl, updateVolume]);
2271
+ const isFillMode = aspectRatio === "fill";
2272
+ const computedAspectRatio = aspectRatio === "auto" || aspectRatio === "fill" ? void 0 : aspectRatio;
2273
+ const renderErrorFallback = /* @__PURE__ */ __name(() => {
2274
+ const fallbackProps = { error: errorMessage, retry };
2275
+ if (typeof errorFallback === "function") {
2276
+ return errorFallback(fallbackProps);
2277
+ }
2278
+ if (errorFallback) {
2279
+ return errorFallback;
2280
+ }
2281
+ return /* @__PURE__ */ jsx(DefaultErrorFallback2, { ...fallbackProps });
2282
+ }, "renderErrorFallback");
2283
+ if (!videoUrl || hasError) {
2284
+ if (isFillMode) {
2285
+ return /* @__PURE__ */ jsx("div", { className: cn("relative w-full h-full overflow-hidden bg-black", className), children: renderErrorFallback() });
2286
+ }
2287
+ return /* @__PURE__ */ jsx("div", { className: cn("relative overflow-hidden bg-black", className), children: /* @__PURE__ */ jsx(AspectRatio, { ratio: computedAspectRatio, children: renderErrorFallback() }) });
2288
+ }
2289
+ const videoContent = /* @__PURE__ */ jsxs(Fragment, { children: [
2290
+ showPreloader && isLoading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50 z-10", children: /* @__PURE__ */ jsx(Preloader, { size: "lg", spinnerClassName: "text-white" }) }),
2291
+ /* @__PURE__ */ jsx(
2292
+ "video",
2293
+ {
2294
+ ref: videoRef,
2295
+ src: videoUrl,
2296
+ className: cn(
2297
+ "w-full h-full object-contain",
2298
+ isLoading && "opacity-0",
2299
+ videoClassName
2300
+ ),
2301
+ autoPlay,
2302
+ muted,
2303
+ loop,
2304
+ playsInline,
2305
+ preload,
2306
+ controls,
2307
+ crossOrigin: "anonymous",
2308
+ poster: source.poster,
2309
+ onContextMenu: handleContextMenu,
2310
+ onLoadStart,
2311
+ onCanPlay: handleCanPlay,
2312
+ onLoadedData: handleLoadedData,
2313
+ onPlay,
2314
+ onPause: handlePause,
2315
+ onEnded,
2316
+ onError: handleError,
2317
+ onTimeUpdate: handleTimeUpdate,
2318
+ onProgress: handleProgress
2319
+ }
2320
+ )
2321
+ ] });
2322
+ if (isFillMode) {
2323
+ return /* @__PURE__ */ jsx("div", { className: cn("relative w-full h-full overflow-hidden bg-black", className), children: videoContent });
2324
+ }
2325
+ return /* @__PURE__ */ jsx("div", { className: cn("relative overflow-hidden bg-black", className), children: /* @__PURE__ */ jsx(AspectRatio, { ratio: computedAspectRatio, children: videoContent }) });
2326
+ }
2327
+ );
2328
+ StreamProvider.displayName = "StreamProvider";
2329
+ var VideoPlayerContext = createContext(null);
2330
+ function VideoPlayerProvider({
2331
+ children,
2332
+ getStreamUrl,
2333
+ sessionId
2334
+ }) {
2335
+ const value = useMemo(
2336
+ () => ({ getStreamUrl, sessionId }),
2337
+ [getStreamUrl, sessionId]
2338
+ );
2339
+ return /* @__PURE__ */ jsx(VideoPlayerContext.Provider, { value, children });
2340
+ }
2341
+ __name(VideoPlayerProvider, "VideoPlayerProvider");
2342
+ function useVideoPlayerContext() {
2343
+ return useContext(VideoPlayerContext);
2344
+ }
2345
+ __name(useVideoPlayerContext, "useVideoPlayerContext");
2346
+
2347
+ // src/tools/VideoPlayer/utils/resolvers.ts
2348
+ function resolvePlayerMode(source, mode = "auto") {
2349
+ if (mode !== "auto") {
2350
+ return mode;
2351
+ }
2352
+ switch (source.type) {
2353
+ case "youtube":
2354
+ case "vimeo":
2355
+ case "hls":
2356
+ case "dash":
2357
+ return "vidstack";
2358
+ case "stream":
2359
+ case "blob":
2360
+ return "streaming";
2361
+ case "data-url":
2362
+ case "url":
2363
+ default:
2364
+ return "native";
2365
+ }
2366
+ }
2367
+ __name(resolvePlayerMode, "resolvePlayerMode");
2368
+ function isSimpleStreamSource(source) {
2369
+ return source.type === "stream" && !("getStreamUrl" in source);
2370
+ }
2371
+ __name(isSimpleStreamSource, "isSimpleStreamSource");
2372
+ function resolveStreamSource(source, context) {
2373
+ if (!context?.getStreamUrl) {
2374
+ console.warn(
2375
+ "VideoPlayer: Stream source requires getStreamUrl. Either provide it in source or wrap with VideoPlayerProvider."
2376
+ );
2377
+ return null;
2378
+ }
2379
+ const sessionId = source.sessionId || context.sessionId;
2380
+ if (!sessionId) {
2381
+ console.warn("VideoPlayer: Stream source requires sessionId.");
2382
+ return null;
2383
+ }
2384
+ return {
2385
+ type: "stream",
2386
+ sessionId,
2387
+ path: source.path,
2388
+ getStreamUrl: context.getStreamUrl,
2389
+ mimeType: source.mimeType,
2390
+ title: source.title,
2391
+ poster: source.poster
2392
+ };
2393
+ }
2394
+ __name(resolveStreamSource, "resolveStreamSource");
2395
+
2396
+ // src/tools/VideoPlayer/utils/fileSource.ts
2397
+ function resolveFileSource(options) {
2398
+ const {
2399
+ content,
2400
+ path,
2401
+ mimeType,
2402
+ sessionId,
2403
+ loadMethod,
2404
+ getStreamUrl,
2405
+ title,
2406
+ poster
2407
+ } = options;
2408
+ const contentSize = content ? typeof content === "string" ? content.length : content.byteLength : 0;
2409
+ const hasContent = contentSize > 0;
2410
+ if (loadMethod === "http_stream" && !hasContent && sessionId && getStreamUrl) {
2411
+ return {
2412
+ type: "stream",
2413
+ sessionId,
2414
+ path,
2415
+ getStreamUrl,
2416
+ mimeType,
2417
+ title,
2418
+ poster
2419
+ };
2420
+ }
2421
+ if (typeof content === "string" && hasContent) {
2422
+ return {
2423
+ type: "data-url",
2424
+ data: content,
2425
+ title,
2426
+ poster
2427
+ };
2428
+ }
2429
+ if (content instanceof ArrayBuffer && hasContent) {
2430
+ return {
2431
+ type: "blob",
2432
+ data: content,
2433
+ mimeType: mimeType || "video/mp4",
2434
+ title,
2435
+ poster
2436
+ };
2437
+ }
2438
+ return null;
2439
+ }
2440
+ __name(resolveFileSource, "resolveFileSource");
2441
+ var VideoPlayer = forwardRef(
2442
+ ({
2443
+ source: rawSource,
2444
+ mode = "auto",
2445
+ aspectRatio = 16 / 9,
2446
+ autoPlay = false,
2447
+ muted = false,
2448
+ loop = false,
2449
+ playsInline = true,
2450
+ controls = true,
2451
+ preload = "metadata",
2452
+ theme = "default",
2453
+ showInfo = false,
2454
+ className,
2455
+ videoClassName,
2456
+ disableContextMenu = false,
2457
+ showPreloader = true,
2458
+ preloaderTimeout = 5e3,
2459
+ errorFallback,
2460
+ onPlay,
2461
+ onPause,
2462
+ onEnded,
2463
+ onError,
2464
+ onLoadStart,
2465
+ onCanPlay,
2466
+ onTimeUpdate
2467
+ }, ref) => {
2468
+ const context = useVideoPlayerContext();
2469
+ const source = useMemo(() => {
2470
+ if (isSimpleStreamSource(rawSource)) {
2471
+ const resolved = resolveStreamSource(rawSource, context);
2472
+ if (!resolved) {
2473
+ return null;
2474
+ }
2475
+ return resolved;
2476
+ }
2477
+ return rawSource;
2478
+ }, [rawSource, context]);
2479
+ if (!source) {
2480
+ const errorMessage = "Stream source requires VideoPlayerProvider with getStreamUrl and sessionId";
2481
+ if (typeof errorFallback === "function") {
2482
+ return /* @__PURE__ */ jsx("div", { className, style: { aspectRatio: aspectRatio === "fill" ? void 0 : aspectRatio }, children: errorFallback({ error: errorMessage }) });
2483
+ }
2484
+ if (errorFallback) {
2485
+ return /* @__PURE__ */ jsx("div", { className, style: { aspectRatio: aspectRatio === "fill" ? void 0 : aspectRatio }, children: errorFallback });
2486
+ }
2487
+ return /* @__PURE__ */ jsx(
2488
+ "div",
2489
+ {
2490
+ className,
2491
+ style: {
2492
+ aspectRatio: aspectRatio === "fill" ? void 0 : aspectRatio,
2493
+ display: "flex",
2494
+ alignItems: "center",
2495
+ justifyContent: "center",
2496
+ backgroundColor: "black",
2497
+ color: "white"
2498
+ },
2499
+ children: /* @__PURE__ */ jsx("p", { children: errorMessage })
2500
+ }
2501
+ );
2502
+ }
2503
+ const resolvedMode = resolvePlayerMode(source, mode);
2504
+ const commonProps = {
2505
+ aspectRatio,
2506
+ autoPlay,
2507
+ muted,
2508
+ loop,
2509
+ playsInline,
2510
+ controls,
2511
+ preload,
2512
+ className,
2513
+ onPlay,
2514
+ onPause,
2515
+ onEnded,
2516
+ onError,
2517
+ onLoadStart,
2518
+ onCanPlay,
2519
+ onTimeUpdate
2520
+ };
2521
+ switch (resolvedMode) {
2522
+ case "vidstack":
2523
+ return /* @__PURE__ */ jsx(
2524
+ VidstackProvider,
2525
+ {
2526
+ ref,
2527
+ source,
2528
+ theme,
2529
+ showInfo,
2530
+ errorFallback,
2531
+ ...commonProps
2532
+ }
2533
+ );
2534
+ case "streaming":
2535
+ return /* @__PURE__ */ jsx(
2536
+ StreamProvider,
2537
+ {
2538
+ ref,
2539
+ source,
2540
+ videoClassName,
2541
+ disableContextMenu,
2542
+ showPreloader,
2543
+ preloaderTimeout,
2544
+ errorFallback,
2545
+ ...commonProps
2546
+ }
2547
+ );
2548
+ case "native":
2549
+ default:
2550
+ return /* @__PURE__ */ jsx(
2551
+ NativeProvider,
2552
+ {
2553
+ ref,
2554
+ source,
2555
+ videoClassName,
2556
+ disableContextMenu,
2557
+ showPreloader,
2558
+ preloaderTimeout,
2559
+ ...commonProps
2560
+ }
2561
+ );
2562
+ }
2563
+ }
2564
+ );
2565
+ VideoPlayer.displayName = "VideoPlayer";
2566
+ function VideoControls({ player, className }) {
2567
+ const store = useMediaStore(player);
2568
+ const remote = useMediaRemote();
2569
+ const isPaused = store.paused;
2570
+ const isMuted = store.muted;
2571
+ const isFullscreen = store.fullscreen;
2572
+ const currentTime = store.currentTime;
2573
+ const duration = store.duration;
2574
+ const volume = store.volume;
2575
+ const formatTime2 = /* @__PURE__ */ __name((seconds) => {
2576
+ if (!seconds || seconds < 0) return "0:00";
2577
+ const minutes = Math.floor(seconds / 60);
2578
+ const secs = Math.floor(seconds % 60);
2579
+ return `${minutes}:${secs.toString().padStart(2, "0")}`;
2580
+ }, "formatTime");
2581
+ const handleProgressClick = /* @__PURE__ */ __name((e) => {
2582
+ if (!duration) return;
2583
+ const rect = e.currentTarget.getBoundingClientRect();
2584
+ const clickX = e.clientX - rect.left;
2585
+ const percentage = clickX / rect.width;
2586
+ const newTime = percentage * duration;
2587
+ remote.seek(newTime);
2588
+ }, "handleProgressClick");
2589
+ const handleVolumeChange = /* @__PURE__ */ __name((e) => {
2590
+ const rect = e.currentTarget.getBoundingClientRect();
2591
+ const clickX = e.clientX - rect.left;
2592
+ const percentage = Math.max(0, Math.min(1, clickX / rect.width));
2593
+ remote.changeVolume(percentage);
2594
+ if (percentage > 0 && isMuted) {
2595
+ remote.toggleMuted();
2596
+ }
2597
+ }, "handleVolumeChange");
2598
+ const progress = duration > 0 ? currentTime / duration * 100 : 0;
2599
+ return /* @__PURE__ */ jsxs(
2600
+ "div",
2601
+ {
2602
+ className: cn(
2603
+ "absolute inset-0 flex flex-col justify-end transition-opacity duration-300",
2604
+ "bg-gradient-to-t from-black/80 via-black/20 to-transparent",
2605
+ "opacity-0 group-hover:opacity-100 focus-within:opacity-100",
2606
+ "pointer-events-none group-hover:pointer-events-auto",
2607
+ className
2608
+ ),
2609
+ children: [
2610
+ /* @__PURE__ */ jsx("div", { className: "px-4 pb-2 pointer-events-auto", children: /* @__PURE__ */ jsx(
2611
+ "div",
2612
+ {
2613
+ className: "h-1.5 bg-white/20 rounded-full cursor-pointer hover:h-2 transition-all group",
2614
+ onClick: handleProgressClick,
2615
+ children: /* @__PURE__ */ jsx(
2616
+ "div",
2617
+ {
2618
+ className: "h-full bg-primary rounded-full transition-all relative group-hover:bg-primary/90",
2619
+ style: { width: `${progress}%` },
2620
+ children: /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-1/2 -translate-y-1/2 w-3 h-3 bg-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity" })
2621
+ }
2622
+ )
2623
+ }
2624
+ ) }),
2625
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 px-4 pb-4 pointer-events-auto", children: [
2626
+ /* @__PURE__ */ jsx(
2627
+ "button",
2628
+ {
2629
+ onClick: () => remote.togglePaused(),
2630
+ className: "text-white hover:text-primary transition-colors p-1.5 hover:bg-white/10 rounded-full",
2631
+ children: isPaused ? /* @__PURE__ */ jsx(Play, { className: "h-6 w-6" }) : /* @__PURE__ */ jsx(Pause, { className: "h-6 w-6" })
2632
+ }
2633
+ ),
2634
+ /* @__PURE__ */ jsxs("div", { className: "text-white text-sm font-medium", children: [
2635
+ formatTime2(currentTime),
2636
+ " / ",
2637
+ formatTime2(duration)
2638
+ ] }),
2639
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
2640
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 group/volume", children: [
2641
+ /* @__PURE__ */ jsx(
2642
+ "button",
2643
+ {
2644
+ onClick: () => remote.toggleMuted(),
2645
+ className: "text-white hover:text-primary transition-colors p-1.5 hover:bg-white/10 rounded-full",
2646
+ children: isMuted || volume === 0 ? /* @__PURE__ */ jsx(VolumeX, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Volume2, { className: "h-5 w-5" })
2647
+ }
2648
+ ),
2649
+ /* @__PURE__ */ jsx(
2650
+ "div",
2651
+ {
2652
+ className: "w-0 group-hover/volume:w-20 transition-all overflow-hidden",
2653
+ children: /* @__PURE__ */ jsx(
2654
+ "div",
2655
+ {
2656
+ className: "h-1.5 bg-white/20 rounded-full cursor-pointer hover:h-2 transition-all",
2657
+ onClick: handleVolumeChange,
2658
+ children: /* @__PURE__ */ jsx(
2659
+ "div",
2660
+ {
2661
+ className: "h-full bg-white rounded-full transition-all",
2662
+ style: { width: `${volume * 100}%` }
2663
+ }
2664
+ )
2665
+ }
2666
+ )
2667
+ }
2668
+ )
2669
+ ] }),
2670
+ /* @__PURE__ */ jsx(
2671
+ "button",
2672
+ {
2673
+ onClick: () => isFullscreen ? remote.exitFullscreen() : remote.enterFullscreen(),
2674
+ className: "text-white hover:text-primary transition-colors p-1.5 hover:bg-white/10 rounded-full",
2675
+ children: isFullscreen ? /* @__PURE__ */ jsx(Minimize, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Maximize, { className: "h-5 w-5" })
2676
+ }
2677
+ )
2678
+ ] })
2679
+ ]
2680
+ }
2681
+ );
2682
+ }
2683
+ __name(VideoControls, "VideoControls");
2684
+ function VideoErrorFallback({
2685
+ error,
2686
+ retry,
2687
+ downloadUrl,
2688
+ downloadFilename,
2689
+ fileSize,
2690
+ showRetry = true,
2691
+ className,
2692
+ icon,
2693
+ title,
2694
+ description
2695
+ }) {
2696
+ const displayTitle = title || error || "Video cannot be previewed";
2697
+ return /* @__PURE__ */ jsxs(
2698
+ "div",
2699
+ {
2700
+ className: cn(
2701
+ "absolute inset-0 flex flex-col items-center justify-center gap-4 bg-black/90 text-white p-6",
2702
+ className
2703
+ ),
2704
+ children: [
2705
+ icon || /* @__PURE__ */ jsx(FileVideo, { className: "w-16 h-16 text-muted-foreground" }),
2706
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-medium text-center", children: displayTitle }),
2707
+ (description || fileSize) && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground text-center", children: description || fileSize }),
2708
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mt-2", children: [
2709
+ showRetry && retry && /* @__PURE__ */ jsxs(
2710
+ Button,
2711
+ {
2712
+ variant: "outline",
2713
+ size: "sm",
2714
+ onClick: retry,
2715
+ className: "gap-2",
2716
+ children: [
2717
+ /* @__PURE__ */ jsx(RefreshCw, { className: "w-4 h-4" }),
2718
+ "Retry"
2719
+ ]
2720
+ }
2721
+ ),
2722
+ downloadUrl && /* @__PURE__ */ jsx(
2723
+ DownloadButton,
2724
+ {
2725
+ url: downloadUrl,
2726
+ filename: downloadFilename,
2727
+ variant: "default",
2728
+ size: "sm",
2729
+ children: "Download to view"
2730
+ }
2731
+ )
2732
+ ] })
2733
+ ]
2734
+ }
2735
+ );
2736
+ }
2737
+ __name(VideoErrorFallback, "VideoErrorFallback");
2738
+ function createVideoErrorFallback(options) {
2739
+ return (props, source) => /* @__PURE__ */ jsx(
2740
+ VideoErrorFallback,
2741
+ {
2742
+ ...props,
2743
+ downloadUrl: options.getDownloadUrl?.(source),
2744
+ downloadFilename: options.getFilename?.(source),
2745
+ fileSize: options.getFileSize?.(source),
2746
+ showRetry: options.showRetry
2747
+ }
2748
+ );
2749
+ }
2750
+ __name(createVideoErrorFallback, "createVideoErrorFallback");
2751
+ function useHybridAudio(options) {
2752
+ const {
2753
+ src,
2754
+ autoPlay = false,
2755
+ initialVolume = 1,
2756
+ loop = false,
2757
+ crossOrigin = "anonymous",
2758
+ onPlay,
2759
+ onPause,
2760
+ onEnded,
2761
+ onTimeUpdate,
2762
+ onError,
2763
+ onReady
2764
+ } = options;
2765
+ const audioRef = useRef(null);
2766
+ const audioContextRef = useRef(null);
2767
+ const sourceNodeRef = useRef(null);
2768
+ const analyserRef = useRef(null);
2769
+ const connectedElementRef = useRef(null);
2770
+ const [state, setState] = useState({
2771
+ isReady: false,
2772
+ isPlaying: false,
2773
+ currentTime: 0,
2774
+ duration: 0,
2775
+ volume: initialVolume,
2776
+ isMuted: false,
2777
+ isLooping: loop,
2778
+ buffered: null,
2779
+ error: null
2780
+ });
2781
+ const initWebAudio = useCallback(() => {
2782
+ const audio = audioRef.current;
2783
+ if (!audio) return;
2784
+ if (connectedElementRef.current === audio && audioContextRef.current) {
2785
+ return;
2786
+ }
2787
+ try {
2788
+ if (!audioContextRef.current) {
2789
+ const AudioContextClass = window.AudioContext || window.webkitAudioContext;
2790
+ audioContextRef.current = new AudioContextClass();
2791
+ }
2792
+ const ctx = audioContextRef.current;
2793
+ if (sourceNodeRef.current) {
2794
+ try {
2795
+ sourceNodeRef.current.disconnect();
2796
+ } catch {
2797
+ }
2798
+ }
2799
+ const source = ctx.createMediaElementSource(audio);
2800
+ sourceNodeRef.current = source;
2801
+ const analyser = ctx.createAnalyser();
2802
+ analyser.fftSize = 256;
2803
+ analyser.smoothingTimeConstant = 0.85;
2804
+ analyserRef.current = analyser;
2805
+ source.connect(ctx.destination);
2806
+ source.connect(analyser);
2807
+ connectedElementRef.current = audio;
2808
+ } catch (error) {
2809
+ console.warn("[useHybridAudio] Web Audio init failed:", error);
2810
+ }
2811
+ }, []);
2812
+ const resumeAudioContext = useCallback(async () => {
2813
+ const ctx = audioContextRef.current;
2814
+ if (ctx && ctx.state === "suspended") {
2815
+ await ctx.resume();
2816
+ }
2817
+ }, []);
2818
+ const play = useCallback(async () => {
2819
+ const audio = audioRef.current;
2820
+ if (!audio) return;
2821
+ try {
2822
+ initWebAudio();
2823
+ await resumeAudioContext();
2824
+ await audio.play();
2825
+ } catch (error) {
2826
+ console.error("[useHybridAudio] Play failed:", error);
2827
+ onError?.(error);
2828
+ }
2829
+ }, [initWebAudio, resumeAudioContext, onError]);
2830
+ const pause = useCallback(() => {
2831
+ audioRef.current?.pause();
2832
+ }, []);
2833
+ const togglePlay = useCallback(() => {
2834
+ if (state.isPlaying) {
2835
+ pause();
2836
+ } else {
2837
+ play();
2838
+ }
2839
+ }, [state.isPlaying, play, pause]);
2840
+ const seek = useCallback(
2841
+ (time) => {
2842
+ const audio = audioRef.current;
2843
+ if (audio && isFinite(time)) {
2844
+ audio.currentTime = Math.max(0, Math.min(time, state.duration || audio.duration || 0));
2845
+ }
2846
+ },
2847
+ [state.duration]
2848
+ );
2849
+ const seekTo = useCallback(
2850
+ (progress) => {
2851
+ const duration = state.duration || audioRef.current?.duration || 0;
2852
+ seek(duration * Math.max(0, Math.min(1, progress)));
2853
+ },
2854
+ [state.duration, seek]
2855
+ );
2856
+ const skip = useCallback(
2857
+ (seconds) => {
2858
+ seek(state.currentTime + seconds);
2859
+ },
2860
+ [state.currentTime, seek]
2861
+ );
2862
+ const setVolume = useCallback((vol) => {
2863
+ const audio = audioRef.current;
2864
+ if (audio) {
2865
+ const clampedVol = Math.max(0, Math.min(1, vol));
2866
+ audio.volume = clampedVol;
2867
+ setState((prev) => ({ ...prev, volume: clampedVol }));
2868
+ }
2869
+ }, []);
2870
+ const toggleMute = useCallback(() => {
2871
+ const audio = audioRef.current;
2872
+ if (audio) {
2873
+ audio.muted = !audio.muted;
2874
+ setState((prev) => ({ ...prev, isMuted: audio.muted }));
2875
+ }
2876
+ }, []);
2877
+ const toggleLoop = useCallback(() => {
2878
+ const audio = audioRef.current;
2879
+ if (audio) {
2880
+ audio.loop = !audio.loop;
2881
+ setState((prev) => ({ ...prev, isLooping: audio.loop }));
2882
+ }
2883
+ }, []);
2884
+ const setLoop = useCallback((enabled) => {
2885
+ const audio = audioRef.current;
2886
+ if (audio) {
2887
+ audio.loop = enabled;
2888
+ setState((prev) => ({ ...prev, isLooping: enabled }));
2889
+ }
2890
+ }, []);
2891
+ const restart = useCallback(() => {
2892
+ seek(0);
2893
+ play();
2894
+ }, [seek, play]);
2895
+ const controls = {
2896
+ play,
2897
+ pause,
2898
+ togglePlay,
2899
+ seek,
2900
+ seekTo,
2901
+ skip,
2902
+ setVolume,
2903
+ toggleMute,
2904
+ toggleLoop,
2905
+ setLoop,
2906
+ restart
2907
+ };
2908
+ useEffect(() => {
2909
+ const audio = document.createElement("audio");
2910
+ audio.preload = "metadata";
2911
+ audio.crossOrigin = crossOrigin;
2912
+ audio.volume = initialVolume;
2913
+ audio.loop = loop;
2914
+ audioRef.current = audio;
2915
+ return () => {
2916
+ audio.pause();
2917
+ audio.src = "";
2918
+ if (audioContextRef.current) {
2919
+ audioContextRef.current.close().catch(() => {
2920
+ });
2921
+ }
2922
+ };
2923
+ }, []);
2924
+ useEffect(() => {
2925
+ const audio = audioRef.current;
2926
+ if (!audio) return;
2927
+ const handlers = {
2928
+ loadedmetadata: /* @__PURE__ */ __name(() => {
2929
+ setState((prev) => ({
2930
+ ...prev,
2931
+ duration: audio.duration,
2932
+ isReady: true
2933
+ }));
2934
+ onReady?.();
2935
+ }, "loadedmetadata"),
2936
+ canplay: /* @__PURE__ */ __name(() => {
2937
+ setState((prev) => ({ ...prev, isReady: true }));
2938
+ if (autoPlay) {
2939
+ play();
2940
+ }
2941
+ }, "canplay"),
2942
+ play: /* @__PURE__ */ __name(() => {
2943
+ setState((prev) => ({ ...prev, isPlaying: true }));
2944
+ onPlay?.();
2945
+ }, "play"),
2946
+ pause: /* @__PURE__ */ __name(() => {
2947
+ setState((prev) => ({ ...prev, isPlaying: false }));
2948
+ onPause?.();
2949
+ }, "pause"),
2950
+ ended: /* @__PURE__ */ __name(() => {
2951
+ setState((prev) => ({ ...prev, isPlaying: false }));
2952
+ onEnded?.();
2953
+ }, "ended"),
2954
+ timeupdate: /* @__PURE__ */ __name(() => {
2955
+ setState((prev) => ({ ...prev, currentTime: audio.currentTime }));
2956
+ onTimeUpdate?.(audio.currentTime);
2957
+ }, "timeupdate"),
2958
+ progress: /* @__PURE__ */ __name(() => {
2959
+ setState((prev) => ({ ...prev, buffered: audio.buffered }));
2960
+ }, "progress"),
2961
+ error: /* @__PURE__ */ __name(() => {
2962
+ const error = new Error(audio.error?.message || "Audio error");
2963
+ setState((prev) => ({ ...prev, error }));
2964
+ onError?.(error);
2965
+ }, "error"),
2966
+ volumechange: /* @__PURE__ */ __name(() => {
2967
+ setState((prev) => ({
2968
+ ...prev,
2969
+ volume: audio.volume,
2970
+ isMuted: audio.muted
2971
+ }));
2972
+ }, "volumechange")
2973
+ };
2974
+ Object.entries(handlers).forEach(([event, handler]) => {
2975
+ audio.addEventListener(event, handler);
2976
+ });
2977
+ return () => {
2978
+ Object.entries(handlers).forEach(([event, handler]) => {
2979
+ audio.removeEventListener(event, handler);
2980
+ });
2981
+ };
2982
+ }, [autoPlay, onPlay, onPause, onEnded, onTimeUpdate, onError, onReady, play]);
2983
+ useEffect(() => {
2984
+ const audio = audioRef.current;
2985
+ if (!audio || !src) return;
2986
+ setState((prev) => ({
2987
+ ...prev,
2988
+ isReady: false,
2989
+ currentTime: 0,
2990
+ duration: 0,
2991
+ error: null
2992
+ }));
2993
+ audio.src = src;
2994
+ audio.load();
2995
+ }, [src]);
2996
+ return {
2997
+ audioRef,
2998
+ state,
2999
+ controls,
3000
+ webAudio: {
3001
+ context: audioContextRef.current,
3002
+ analyser: analyserRef.current,
3003
+ sourceNode: sourceNodeRef.current
3004
+ }
3005
+ };
3006
+ }
3007
+ __name(useHybridAudio, "useHybridAudio");
3008
+ function useHybridAudioAnalysis(analyser, isPlaying) {
3009
+ const [levels, setLevels] = useState({ bass: 0, mid: 0, high: 0, overall: 0 });
3010
+ const animationRef = useRef(null);
3011
+ const dataArrayRef = useRef(null);
3012
+ const cleanup = useCallback(() => {
3013
+ if (animationRef.current) {
3014
+ cancelAnimationFrame(animationRef.current);
3015
+ animationRef.current = null;
3016
+ }
3017
+ }, []);
3018
+ useEffect(() => {
3019
+ if (analyser && !dataArrayRef.current) {
3020
+ dataArrayRef.current = new Uint8Array(analyser.frequencyBinCount);
3021
+ }
3022
+ }, [analyser]);
3023
+ useEffect(() => {
3024
+ if (!isPlaying || !analyser || !dataArrayRef.current) {
3025
+ cleanup();
3026
+ setLevels((prev) => ({
3027
+ bass: prev.bass * 0.95 < 0.01 ? 0 : prev.bass * 0.95,
3028
+ mid: prev.mid * 0.95 < 0.01 ? 0 : prev.mid * 0.95,
3029
+ high: prev.high * 0.95 < 0.01 ? 0 : prev.high * 0.95,
3030
+ overall: prev.overall * 0.95 < 0.01 ? 0 : prev.overall * 0.95
3031
+ }));
3032
+ return;
3033
+ }
3034
+ const dataArray = dataArrayRef.current;
3035
+ const animate = /* @__PURE__ */ __name(() => {
3036
+ analyser.getByteFrequencyData(dataArray);
3037
+ const binCount = dataArray.length;
3038
+ const bassEnd = Math.floor(binCount * 0.15);
3039
+ let bassSum = 0;
3040
+ for (let i = 0; i < bassEnd; i++) bassSum += dataArray[i];
3041
+ const bass = bassSum / bassEnd / 255;
3042
+ const midStart = bassEnd;
3043
+ const midEnd = Math.floor(binCount * 0.5);
3044
+ let midSum = 0;
3045
+ for (let i = midStart; i < midEnd; i++) midSum += dataArray[i];
3046
+ const mid = midSum / (midEnd - midStart) / 255;
3047
+ const highStart = midEnd;
3048
+ let highSum = 0;
3049
+ for (let i = highStart; i < binCount; i++) highSum += dataArray[i];
3050
+ const high = highSum / (binCount - highStart) / 255;
3051
+ let totalSum = 0;
3052
+ for (let i = 0; i < binCount; i++) totalSum += dataArray[i];
3053
+ const overall = totalSum / binCount / 255;
3054
+ setLevels((prev) => ({
3055
+ bass: prev.bass * 0.7 + bass * 0.3,
3056
+ mid: prev.mid * 0.7 + mid * 0.3,
3057
+ high: prev.high * 0.7 + high * 0.3,
3058
+ overall: prev.overall * 0.7 + overall * 0.3
3059
+ }));
3060
+ animationRef.current = requestAnimationFrame(animate);
3061
+ }, "animate");
3062
+ animationRef.current = requestAnimationFrame(animate);
3063
+ return cleanup;
3064
+ }, [analyser, isPlaying, cleanup]);
3065
+ return levels;
3066
+ }
3067
+ __name(useHybridAudioAnalysis, "useHybridAudioAnalysis");
3068
+ var STORAGE_KEY = "audio-player-settings";
3069
+ var DEFAULT_SETTINGS = {
3070
+ enabled: true,
3071
+ variant: "spotlight",
3072
+ intensity: "medium",
3073
+ colorScheme: "primary",
3074
+ volume: 1,
3075
+ isLooping: false
3076
+ };
3077
+ var VARIANTS = ["spotlight", "glow", "orbs", "mesh", "none"];
3078
+ var INTENSITIES = ["subtle", "medium", "strong"];
3079
+ var COLOR_SCHEMES = ["primary", "vibrant", "cool", "warm"];
3080
+ var VisualizationContext = createContext(null);
3081
+ function VisualizationProvider({ children }) {
3082
+ const value = useVisualizationState();
3083
+ return /* @__PURE__ */ jsx(VisualizationContext.Provider, { value, children });
3084
+ }
3085
+ __name(VisualizationProvider, "VisualizationProvider");
3086
+ function useVisualizationState() {
3087
+ const [settings, setSettings] = useLocalStorage(
3088
+ STORAGE_KEY,
3089
+ DEFAULT_SETTINGS
3090
+ );
3091
+ const toggle = useCallback(() => {
3092
+ setSettings((prev) => ({ ...prev, enabled: !prev.enabled }));
3093
+ }, [setSettings]);
3094
+ const setSetting = useCallback(
3095
+ (key, value) => {
3096
+ setSettings((prev) => ({ ...prev, [key]: value }));
3097
+ },
3098
+ [setSettings]
3099
+ );
3100
+ const nextVariant = useCallback(() => {
3101
+ setSettings((prev) => {
3102
+ const currentIndex = VARIANTS.indexOf(prev.variant);
3103
+ const nextIndex = (currentIndex + 1) % VARIANTS.length;
3104
+ return { ...prev, variant: VARIANTS[nextIndex] };
3105
+ });
3106
+ }, [setSettings]);
3107
+ const nextIntensity = useCallback(() => {
3108
+ setSettings((prev) => {
3109
+ const currentIndex = INTENSITIES.indexOf(prev.intensity);
3110
+ const nextIndex = (currentIndex + 1) % INTENSITIES.length;
3111
+ return { ...prev, intensity: INTENSITIES[nextIndex] };
3112
+ });
3113
+ }, [setSettings]);
3114
+ const nextColorScheme = useCallback(() => {
3115
+ setSettings((prev) => {
3116
+ const currentIndex = COLOR_SCHEMES.indexOf(prev.colorScheme);
3117
+ const nextIndex = (currentIndex + 1) % COLOR_SCHEMES.length;
3118
+ return { ...prev, colorScheme: COLOR_SCHEMES[nextIndex] };
3119
+ });
3120
+ }, [setSettings]);
3121
+ const reset = useCallback(() => {
3122
+ setSettings(DEFAULT_SETTINGS);
3123
+ }, [setSettings]);
3124
+ return useMemo(
3125
+ () => ({
3126
+ settings,
3127
+ toggle,
3128
+ setSetting,
3129
+ nextVariant,
3130
+ nextIntensity,
3131
+ nextColorScheme,
3132
+ reset
3133
+ }),
3134
+ [settings, toggle, setSetting, nextVariant, nextIntensity, nextColorScheme, reset]
3135
+ );
3136
+ }
3137
+ __name(useVisualizationState, "useVisualizationState");
3138
+ function useVisualization() {
3139
+ const context = useContext(VisualizationContext);
3140
+ const fallbackState = useVisualizationState();
3141
+ return context ?? fallbackState;
3142
+ }
3143
+ __name(useVisualization, "useVisualization");
3144
+ var useAudioVisualization = useVisualization;
3145
+ var VARIANT_INFO = {
3146
+ spotlight: { label: "Spotlight", icon: "\u{1F4AB}" },
3147
+ glow: { label: "Glow", icon: "\u2728" },
3148
+ orbs: { label: "Orbs", icon: "\u{1F52E}" },
3149
+ mesh: { label: "Mesh", icon: "\u{1F308}" },
3150
+ none: { label: "Off", icon: "\u2B55" }
3151
+ };
3152
+ var INTENSITY_INFO = {
3153
+ subtle: { label: "Subtle" },
3154
+ medium: { label: "Medium" },
3155
+ strong: { label: "Strong" }
3156
+ };
3157
+ var COLOR_SCHEME_INFO = {
3158
+ primary: { label: "Primary", preview: "\u{1F535}" },
3159
+ vibrant: { label: "Vibrant", preview: "\u{1F308}" },
3160
+ cool: { label: "Cool", preview: "\u{1F499}" },
3161
+ warm: { label: "Warm", preview: "\u{1F525}" }
3162
+ };
3163
+ var HybridAudioContext = createContext(null);
3164
+ function HybridAudioProvider({ children, ...options }) {
3165
+ const { settings: savedSettings, setSetting } = useVisualization();
3166
+ const effectiveOptions = {
3167
+ ...options,
3168
+ initialVolume: savedSettings.volume,
3169
+ loop: savedSettings.isLooping
3170
+ };
3171
+ const { audioRef, state, controls, webAudio } = useHybridAudio(effectiveOptions);
3172
+ const hasAppliedInitialSettings = useRef(false);
3173
+ useEffect(() => {
3174
+ if (!state.isReady || hasAppliedInitialSettings.current) return;
3175
+ hasAppliedInitialSettings.current = true;
3176
+ controls.setVolume(savedSettings.volume);
3177
+ controls.setLoop(savedSettings.isLooping);
3178
+ }, [state.isReady, savedSettings, controls]);
3179
+ useEffect(() => {
3180
+ if (!state.isReady) return;
3181
+ if (state.volume !== savedSettings.volume) {
3182
+ setSetting("volume", state.volume);
3183
+ }
3184
+ if (state.isLooping !== savedSettings.isLooping) {
3185
+ setSetting("isLooping", state.isLooping);
3186
+ }
3187
+ }, [state.isReady, state.volume, state.isLooping, savedSettings, setSetting]);
3188
+ const audioLevels = useHybridAudioAnalysis(webAudio.analyser, state.isPlaying);
3189
+ const value = useMemo(
3190
+ () => ({
3191
+ state,
3192
+ controls,
3193
+ webAudio,
3194
+ audioLevels,
3195
+ audioRef
3196
+ }),
3197
+ [state, controls, webAudio, audioLevels, audioRef]
3198
+ );
3199
+ return /* @__PURE__ */ jsx(HybridAudioContext.Provider, { value, children });
3200
+ }
3201
+ __name(HybridAudioProvider, "HybridAudioProvider");
3202
+ function useHybridAudioContext() {
3203
+ const context = useContext(HybridAudioContext);
3204
+ if (!context) {
3205
+ throw new Error("useHybridAudioContext must be used within HybridAudioProvider");
3206
+ }
3207
+ return context;
3208
+ }
3209
+ __name(useHybridAudioContext, "useHybridAudioContext");
3210
+ function useHybridAudioState() {
3211
+ const { state } = useHybridAudioContext();
3212
+ return state;
3213
+ }
3214
+ __name(useHybridAudioState, "useHybridAudioState");
3215
+ function useHybridAudioControls() {
3216
+ const { controls } = useHybridAudioContext();
3217
+ return controls;
3218
+ }
3219
+ __name(useHybridAudioControls, "useHybridAudioControls");
3220
+ function useHybridAudioLevels() {
3221
+ const { audioLevels } = useHybridAudioContext();
3222
+ return audioLevels;
3223
+ }
3224
+ __name(useHybridAudioLevels, "useHybridAudioLevels");
3225
+ function useHybridWebAudio() {
3226
+ const { webAudio } = useHybridAudioContext();
3227
+ return webAudio;
3228
+ }
3229
+ __name(useHybridWebAudio, "useHybridWebAudio");
3230
+ var HybridWaveform = memo(/* @__PURE__ */ __name(function HybridWaveform2({
3231
+ mode = "frequency",
3232
+ height = 64,
3233
+ barWidth = 3,
3234
+ barGap = 2,
3235
+ barRadius = 2,
3236
+ progressColor = "hsl(217 91% 60%)",
3237
+ waveColor = "hsl(217 91% 60% / 0.3)",
3238
+ bufferedColor = "hsl(217 91% 60% / 0.15)",
3239
+ className,
3240
+ onSeek
3241
+ }) {
3242
+ const canvasRef = useRef(null);
3243
+ const containerRef = useRef(null);
3244
+ const animationRef = useRef(null);
3245
+ const { state, controls, webAudio } = useHybridAudioContext();
3246
+ const handleClick = useCallback(
3247
+ (e) => {
3248
+ const canvas = canvasRef.current;
3249
+ if (!canvas || !state.duration) return;
3250
+ const rect = canvas.getBoundingClientRect();
3251
+ const x = e.clientX - rect.left;
3252
+ const progress = x / rect.width;
3253
+ const time = state.duration * progress;
3254
+ controls.seek(time);
3255
+ onSeek?.(time);
3256
+ },
3257
+ [state.duration, controls, onSeek]
3258
+ );
3259
+ const renderFrequency = useCallback(() => {
3260
+ const canvas = canvasRef.current;
3261
+ const analyser = webAudio.analyser;
3262
+ if (!canvas) return;
3263
+ const ctx = canvas.getContext("2d");
3264
+ if (!ctx) return;
3265
+ const { width, height: canvasHeight } = canvas;
3266
+ const dpr = window.devicePixelRatio || 1;
3267
+ const displayWidth = width / dpr;
3268
+ let dataArray = null;
3269
+ if (analyser) {
3270
+ dataArray = new Uint8Array(analyser.frequencyBinCount);
3271
+ analyser.getByteFrequencyData(dataArray);
3272
+ }
3273
+ ctx.clearRect(0, 0, width, canvasHeight);
3274
+ if (state.buffered && state.duration > 0) {
3275
+ ctx.fillStyle = bufferedColor;
3276
+ for (let i = 0; i < state.buffered.length; i++) {
3277
+ const start = state.buffered.start(i) / state.duration * width;
3278
+ const end = state.buffered.end(i) / state.duration * width;
3279
+ ctx.fillRect(start, canvasHeight - 3 * dpr, end - start, 3 * dpr);
3280
+ }
3281
+ }
3282
+ const barCount = Math.floor(displayWidth / (barWidth + barGap));
3283
+ const progress = state.duration > 0 ? state.currentTime / state.duration : 0;
3284
+ const progressX = width * progress;
3285
+ for (let i = 0; i < barCount; i++) {
3286
+ let barHeight;
3287
+ if (dataArray && state.isPlaying) {
3288
+ const step = Math.floor(dataArray.length / barCount);
3289
+ const value = dataArray[i * step] / 255;
3290
+ barHeight = Math.max(4 * dpr, value * (canvasHeight - 6 * dpr) * 0.9);
3291
+ } else {
3292
+ barHeight = 8 * dpr;
3293
+ }
3294
+ const x = i * (barWidth + barGap) * dpr;
3295
+ const y = (canvasHeight - barHeight) / 2;
3296
+ ctx.fillStyle = x < progressX ? progressColor : waveColor;
3297
+ const radius = barRadius * dpr;
3298
+ const rectWidth = barWidth * dpr;
3299
+ ctx.beginPath();
3300
+ ctx.roundRect(x, y, rectWidth, barHeight, radius);
3301
+ ctx.fill();
3302
+ }
3303
+ if (state.isPlaying) {
3304
+ animationRef.current = requestAnimationFrame(renderFrequency);
3305
+ }
3306
+ }, [
3307
+ webAudio.analyser,
3308
+ state.buffered,
3309
+ state.duration,
3310
+ state.currentTime,
3311
+ state.isPlaying,
3312
+ barWidth,
3313
+ barGap,
3314
+ barRadius,
3315
+ progressColor,
3316
+ waveColor,
3317
+ bufferedColor
3318
+ ]);
3319
+ const renderStatic = useCallback(() => {
3320
+ const canvas = canvasRef.current;
3321
+ if (!canvas) return;
3322
+ const ctx = canvas.getContext("2d");
3323
+ if (!ctx) return;
3324
+ const { width, height: canvasHeight } = canvas;
3325
+ const dpr = window.devicePixelRatio || 1;
3326
+ ctx.clearRect(0, 0, width, canvasHeight);
3327
+ if (state.buffered && state.duration > 0) {
3328
+ ctx.fillStyle = bufferedColor;
3329
+ for (let i = 0; i < state.buffered.length; i++) {
3330
+ const start = state.buffered.start(i) / state.duration * width;
3331
+ const end = state.buffered.end(i) / state.duration * width;
3332
+ ctx.fillRect(start, 0, end - start, canvasHeight);
3333
+ }
3334
+ }
3335
+ const progress = state.duration > 0 ? state.currentTime / state.duration : 0;
3336
+ const progressWidth = width * progress;
3337
+ ctx.fillStyle = waveColor;
3338
+ ctx.fillRect(0, canvasHeight / 2 - 2 * dpr, width, 4 * dpr);
3339
+ ctx.fillStyle = progressColor;
3340
+ ctx.fillRect(0, canvasHeight / 2 - 2 * dpr, progressWidth, 4 * dpr);
3341
+ if (progress > 0) {
3342
+ ctx.beginPath();
3343
+ ctx.arc(progressWidth, canvasHeight / 2, 6 * dpr, 0, Math.PI * 2);
3344
+ ctx.fill();
3345
+ }
3346
+ }, [state.buffered, state.duration, state.currentTime, progressColor, waveColor, bufferedColor]);
3347
+ useEffect(() => {
3348
+ const container = containerRef.current;
3349
+ const canvas = canvasRef.current;
3350
+ if (!container || !canvas) return;
3351
+ const resizeObserver = new ResizeObserver((entries) => {
3352
+ const entry = entries[0];
3353
+ if (!entry) return;
3354
+ const dpr = window.devicePixelRatio || 1;
3355
+ const displayWidth = entry.contentRect.width;
3356
+ const displayHeight = height;
3357
+ canvas.width = displayWidth * dpr;
3358
+ canvas.height = displayHeight * dpr;
3359
+ canvas.style.width = `${displayWidth}px`;
3360
+ canvas.style.height = `${displayHeight}px`;
3361
+ if (mode === "frequency") {
3362
+ renderFrequency();
3363
+ } else {
3364
+ renderStatic();
3365
+ }
3366
+ });
3367
+ resizeObserver.observe(container);
3368
+ return () => resizeObserver.disconnect();
3369
+ }, [height, mode, renderFrequency, renderStatic]);
3370
+ useEffect(() => {
3371
+ if (mode === "frequency") {
3372
+ renderFrequency();
3373
+ }
3374
+ return () => {
3375
+ if (animationRef.current) {
3376
+ cancelAnimationFrame(animationRef.current);
3377
+ }
3378
+ };
3379
+ }, [mode, renderFrequency]);
3380
+ useEffect(() => {
3381
+ if (mode === "static") {
3382
+ renderStatic();
3383
+ }
3384
+ }, [mode, state.currentTime, renderStatic]);
3385
+ useEffect(() => {
3386
+ if (mode === "frequency" && !state.isPlaying) {
3387
+ renderFrequency();
3388
+ }
3389
+ }, [mode, state.isPlaying, renderFrequency]);
3390
+ return /* @__PURE__ */ jsx("div", { ref: containerRef, className: cn("w-full", className), children: /* @__PURE__ */ jsx(
3391
+ "canvas",
3392
+ {
3393
+ ref: canvasRef,
3394
+ onClick: handleClick,
3395
+ className: "cursor-pointer",
3396
+ style: { width: "100%", height }
3397
+ }
3398
+ ) });
3399
+ }, "HybridWaveform"));
3400
+
3401
+ // src/tools/AudioPlayer/utils/formatTime.ts
3402
+ function formatTime(seconds) {
3403
+ if (!seconds || !isFinite(seconds) || seconds < 0) return "0:00";
3404
+ const mins = Math.floor(seconds / 60);
3405
+ const secs = Math.floor(seconds % 60);
3406
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
3407
+ }
3408
+ __name(formatTime, "formatTime");
3409
+ createMediaLogger("AudioPlayer");
3410
+ var HybridAudioPlayer = memo(/* @__PURE__ */ __name(function HybridAudioPlayer2({
3411
+ showControls = true,
3412
+ showWaveform = true,
3413
+ waveformMode = "frequency",
3414
+ waveformHeight = 64,
3415
+ showTimer = true,
3416
+ showVolume = true,
3417
+ showLoop = true,
3418
+ className,
3419
+ style
3420
+ }) {
3421
+ const { state, controls } = useHybridAudioContext();
3422
+ const isLoading = !state.isReady;
3423
+ const handleVolumeChange = /* @__PURE__ */ __name((value) => {
3424
+ controls.setVolume(value[0] / 100);
3425
+ }, "handleVolumeChange");
3426
+ return /* @__PURE__ */ jsxs(
3427
+ "div",
3428
+ {
3429
+ className: cn("flex flex-col gap-3 p-4 rounded-lg bg-card border", className),
3430
+ style,
3431
+ children: [
3432
+ showWaveform && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
3433
+ /* @__PURE__ */ jsx(
3434
+ HybridWaveform,
3435
+ {
3436
+ mode: waveformMode,
3437
+ height: waveformHeight,
3438
+ className: cn(isLoading && "opacity-50")
3439
+ }
3440
+ ),
3441
+ isLoading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(Loader2, { className: "h-6 w-6 animate-spin text-primary" }) })
3442
+ ] }),
3443
+ showTimer && /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-xs text-muted-foreground tabular-nums px-1", children: [
3444
+ /* @__PURE__ */ jsx("span", { children: formatTime(state.currentTime) }),
3445
+ /* @__PURE__ */ jsx("span", { children: formatTime(state.duration) })
3446
+ ] }),
3447
+ showControls && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1", children: [
3448
+ /* @__PURE__ */ jsx(
3449
+ Button,
3450
+ {
3451
+ variant: "ghost",
3452
+ size: "icon",
3453
+ className: "h-9 w-9",
3454
+ onClick: controls.restart,
3455
+ disabled: !state.isReady,
3456
+ title: "Restart",
3457
+ children: /* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" })
3458
+ }
3459
+ ),
3460
+ /* @__PURE__ */ jsx(
3461
+ Button,
3462
+ {
3463
+ variant: "ghost",
3464
+ size: "icon",
3465
+ className: "h-9 w-9",
3466
+ onClick: () => controls.skip(-5),
3467
+ disabled: !state.isReady,
3468
+ title: "Back 5 seconds",
3469
+ children: /* @__PURE__ */ jsx(SkipBack, { className: "h-4 w-4" })
3470
+ }
3471
+ ),
3472
+ /* @__PURE__ */ jsx(
3473
+ Button,
3474
+ {
3475
+ variant: "default",
3476
+ size: "icon",
3477
+ className: "h-12 w-12 rounded-full",
3478
+ onClick: controls.togglePlay,
3479
+ disabled: !state.isReady && !isLoading,
3480
+ title: state.isPlaying ? "Pause" : "Play",
3481
+ children: isLoading ? /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin" }) : state.isPlaying ? /* @__PURE__ */ jsx(Pause, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Play, { className: "h-5 w-5 ml-0.5" })
3482
+ }
3483
+ ),
3484
+ /* @__PURE__ */ jsx(
3485
+ Button,
3486
+ {
3487
+ variant: "ghost",
3488
+ size: "icon",
3489
+ className: "h-9 w-9",
3490
+ onClick: () => controls.skip(5),
3491
+ disabled: !state.isReady,
3492
+ title: "Forward 5 seconds",
3493
+ children: /* @__PURE__ */ jsx(SkipForward, { className: "h-4 w-4" })
3494
+ }
3495
+ ),
3496
+ showVolume && /* @__PURE__ */ jsxs(Fragment, { children: [
3497
+ /* @__PURE__ */ jsx(
3498
+ Button,
3499
+ {
3500
+ variant: "ghost",
3501
+ size: "icon",
3502
+ className: "h-9 w-9",
3503
+ onClick: controls.toggleMute,
3504
+ title: state.isMuted ? "Unmute" : "Mute",
3505
+ children: state.isMuted || state.volume === 0 ? /* @__PURE__ */ jsx(VolumeX, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Volume2, { className: "h-4 w-4" })
3506
+ }
3507
+ ),
3508
+ /* @__PURE__ */ jsx(
3509
+ Slider,
3510
+ {
3511
+ value: [state.isMuted ? 0 : state.volume * 100],
3512
+ max: 100,
3513
+ step: 1,
3514
+ onValueChange: handleVolumeChange,
3515
+ className: "w-20",
3516
+ "aria-label": "Volume"
3517
+ }
3518
+ )
3519
+ ] }),
3520
+ showLoop && /* @__PURE__ */ jsx(
3521
+ Button,
3522
+ {
3523
+ variant: "ghost",
3524
+ size: "icon",
3525
+ className: cn("h-9 w-9", state.isLooping && "text-primary"),
3526
+ onClick: controls.toggleLoop,
3527
+ disabled: !state.isReady,
3528
+ title: state.isLooping ? "Disable loop" : "Enable loop",
3529
+ children: /* @__PURE__ */ jsx(Repeat, { className: "h-4 w-4" })
3530
+ }
3531
+ )
3532
+ ] })
3533
+ ]
3534
+ }
3535
+ );
3536
+ }, "HybridAudioPlayer"));
3537
+
3538
+ // src/tools/AudioPlayer/effects/index.ts
3539
+ var INTENSITY_CONFIG = {
3540
+ subtle: { opacity: 0.3, scale: 0.02, blur: "blur-2xl" },
3541
+ medium: { opacity: 0.5, scale: 0.04, blur: "blur-xl" },
3542
+ strong: { opacity: 0.7, scale: 0.06, blur: "blur-lg" }
3543
+ };
3544
+ var COLOR_SCHEMES2 = {
3545
+ primary: ["217 91% 60%"],
3546
+ vibrant: ["217 91% 60%", "142 76% 36%", "262 83% 58%", "25 95% 53%"],
3547
+ cool: ["217 91% 60%", "262 83% 58%", "199 89% 48%"],
3548
+ warm: ["25 95% 53%", "0 84% 60%", "38 92% 50%"]
3549
+ };
3550
+ var DEFAULT_GLOW_COLORS = [
3551
+ "217 91% 60%",
3552
+ // Blue
3553
+ "262 83% 58%",
3554
+ // Purple
3555
+ "330 81% 60%",
3556
+ // Pink
3557
+ "25 95% 53%"
3558
+ // Orange
3559
+ ];
3560
+ function getEffectConfig(intensity) {
3561
+ return INTENSITY_CONFIG[intensity];
3562
+ }
3563
+ __name(getEffectConfig, "getEffectConfig");
3564
+ function getColors(colorScheme) {
3565
+ return COLOR_SCHEMES2[colorScheme];
3566
+ }
3567
+ __name(getColors, "getColors");
3568
+ function prepareEffectColors(colorScheme, levels) {
3569
+ const baseColors = COLOR_SCHEMES2[colorScheme];
3570
+ const colors = baseColors.length > 1 ? baseColors : DEFAULT_GLOW_COLORS;
3571
+ const hueShift = Math.floor(
3572
+ levels.bass * 30 + levels.mid * 20 + levels.high * 10
3573
+ );
3574
+ return { colors, hueShift };
3575
+ }
3576
+ __name(prepareEffectColors, "prepareEffectColors");
3577
+ function calculateGlowLayers(levels, config, colors) {
3578
+ const { bass, mid, high } = levels;
3579
+ return [
3580
+ // Layer 1: Bass glow - bottom
3581
+ {
3582
+ inset: 60 + bass * 90,
3583
+ opacity: 1,
3584
+ scale: 1 + bass * 0.5,
3585
+ background: `radial-gradient(ellipse 80% 60% at 50% 100%, hsl(${colors[0]} / ${0.4 + bass * 0.4}) 0%, transparent 70%)`,
3586
+ blur: "blur-3xl"
3587
+ },
3588
+ // Layer 2: Mid glow - top
3589
+ {
3590
+ inset: 45 + mid * 75,
3591
+ opacity: 1,
3592
+ scale: 1 + mid * 0.4,
3593
+ background: `radial-gradient(ellipse 70% 50% at 50% 0%, hsl(${colors[1] || colors[0]} / ${0.3 + mid * 0.5}) 0%, transparent 70%)`,
3594
+ blur: "blur-2xl"
3595
+ },
3596
+ // Layer 3: High glow - left
3597
+ {
3598
+ inset: 30 + high * 60,
3599
+ opacity: 1,
3600
+ scale: 1 + high * 0.3,
3601
+ background: `radial-gradient(ellipse 50% 80% at 0% 50%, hsl(${colors[2] || colors[0]} / ${0.3 + high * 0.4}) 0%, transparent 60%)`,
3602
+ blur: "blur-2xl"
3603
+ },
3604
+ // Layer 4: High glow - right
3605
+ {
3606
+ inset: 30 + high * 60,
3607
+ opacity: 1,
3608
+ scale: 1 + high * 0.3,
3609
+ background: `radial-gradient(ellipse 50% 80% at 100% 50%, hsl(${colors[3] || colors[0]} / ${0.3 + high * 0.4}) 0%, transparent 60%)`,
3610
+ blur: "blur-2xl"
3611
+ },
3612
+ // Layer 5: Center pulsing glow
3613
+ {
3614
+ inset: 24 + bass * 45,
3615
+ opacity: 1,
3616
+ scale: 1 + bass * 0.2,
3617
+ background: `radial-gradient(circle at 50% 50%, hsl(${colors[0]} / ${0.2 + bass * 0.3}) 0%, hsl(${colors[1] || colors[0]} / ${0.1 + mid * 0.2}) 40%, transparent 70%)`,
3618
+ blur: "blur-xl",
3619
+ animation: "glow-breathe 2s ease-in-out infinite"
3620
+ }
3621
+ ];
3622
+ }
3623
+ __name(calculateGlowLayers, "calculateGlowLayers");
3624
+ function calculateOrbs(levels, config, colors, baseSize = 50) {
3625
+ const { bass, mid, high, overall } = levels;
3626
+ const size = baseSize * 3;
3627
+ const bassMove = bass * 30;
3628
+ const midMove = mid * 25;
3629
+ const highMove = high * 20;
3630
+ return [
3631
+ // Bass orb - top left, big pulses
3632
+ {
3633
+ x: -40 + bassMove,
3634
+ y: -40 - bassMove * 0.5,
3635
+ size: size * (1 + bass * 1.2),
3636
+ color: colors[0],
3637
+ opacity: 0.5 + bass * 0.5,
3638
+ scale: 1 + bass * 0.6
3639
+ },
3640
+ // Mid orb - top right
3641
+ {
3642
+ x: 130 - midMove * 0.5,
3643
+ y: -30 + midMove,
3644
+ size: size * (0.9 + mid * 1),
3645
+ color: colors[1] || colors[0],
3646
+ opacity: 0.5 + mid * 0.5,
3647
+ scale: 1 + mid * 0.5
3648
+ },
3649
+ // High orb - bottom right
3650
+ {
3651
+ x: 140 + highMove * 0.3,
3652
+ y: 120 - highMove,
3653
+ size: size * (0.8 + high * 0.8),
3654
+ color: colors[2] || colors[0],
3655
+ opacity: 0.4 + high * 0.6,
3656
+ scale: 1 + high * 0.45
3657
+ },
3658
+ // Mid orb 2 - bottom left
3659
+ {
3660
+ x: -30 - midMove * 0.4,
3661
+ y: 130 + midMove * 0.3,
3662
+ size: size * (0.9 + mid * 0.9),
3663
+ color: colors[3] || colors[0],
3664
+ opacity: 0.45 + mid * 0.55,
3665
+ scale: 1 + mid * 0.5
3666
+ },
3667
+ // Center overall orb
3668
+ {
3669
+ x: 50,
3670
+ y: 50,
3671
+ size: size * (0.6 + overall * 1.5),
3672
+ color: colors[0],
3673
+ opacity: 0.3 + overall * 0.5,
3674
+ scale: 1 + overall * 0.7
3675
+ }
3676
+ ];
3677
+ }
3678
+ __name(calculateOrbs, "calculateOrbs");
3679
+ function calculateMeshGradients(levels, config, colors) {
3680
+ const { bass, mid, high, overall } = levels;
3681
+ const bassOffset = bass * 40;
3682
+ const midOffset = mid * 30;
3683
+ const highOffset = high * 25;
3684
+ return [
3685
+ // Large bass blob - top right, pulses hard
3686
+ {
3687
+ width: `${200 + bass * 150}%`,
3688
+ height: `${200 + bass * 150}%`,
3689
+ top: `${ -80 + bassOffset}%`,
3690
+ right: `${ -80 - bassOffset}%`,
3691
+ color: colors[0],
3692
+ opacity: 0.4 + bass * 0.6,
3693
+ scale: 1 + bass * 0.5,
3694
+ rotation: bass * 45,
3695
+ blur: "blur-2xl"
3696
+ },
3697
+ // Mid blob - bottom left
3698
+ {
3699
+ width: `${180 + mid * 120}%`,
3700
+ height: `${180 + mid * 120}%`,
3701
+ bottom: `${ -60 + midOffset}%`,
3702
+ left: `${ -60 - midOffset}%`,
3703
+ color: colors[1] || colors[0],
3704
+ opacity: 0.4 + mid * 0.6,
3705
+ scale: 1 + mid * 0.4,
3706
+ rotation: -mid * 40,
3707
+ blur: "blur-2xl"
3708
+ },
3709
+ // High blob - bottom right
3710
+ {
3711
+ width: `${140 + high * 100}%`,
3712
+ height: `${140 + high * 100}%`,
3713
+ top: `${70 - highOffset}%`,
3714
+ right: `${ -50 + highOffset}%`,
3715
+ color: colors[2] || colors[0],
3716
+ opacity: 0.35 + high * 0.65,
3717
+ scale: 1 + high * 0.35,
3718
+ rotation: high * 35,
3719
+ blur: "blur-xl"
3720
+ },
3721
+ // Extra bass reactive blob - top left
3722
+ {
3723
+ width: `${160 + bass * 140}%`,
3724
+ height: `${160 + bass * 140}%`,
3725
+ top: `${ -60 - bassOffset * 0.8}%`,
3726
+ left: `${ -60 + bassOffset * 0.8}%`,
3727
+ color: colors[3] || colors[1] || colors[0],
3728
+ opacity: 0.35 + bass * 0.65,
3729
+ scale: 1 + bass * 0.55,
3730
+ rotation: -bass * 50,
3731
+ blur: "blur-2xl"
3732
+ },
3733
+ // Center glow - pulses with overall
3734
+ {
3735
+ width: `${80 + overall * 150}%`,
3736
+ height: `${80 + overall * 150}%`,
3737
+ top: "50%",
3738
+ left: "50%",
3739
+ color: colors[0],
3740
+ opacity: 0.3 + overall * 0.5,
3741
+ scale: 1 + overall * 0.4,
3742
+ rotation: 0,
3743
+ isCenter: true,
3744
+ blur: "blur-3xl"
3745
+ }
3746
+ ];
3747
+ }
3748
+ __name(calculateMeshGradients, "calculateMeshGradients");
3749
+ function calculateSpotlight(levels, config, colors, rotation) {
3750
+ const { bass, mid, high, overall } = levels;
3751
+ return {
3752
+ // Rotation speed increases with mid frequencies
3753
+ rotation: rotation + mid * 180,
3754
+ // Border expands with bass
3755
+ inset: 12 + bass * 30,
3756
+ // Color intensities react to different frequencies
3757
+ colors: colors.map((c, i) => ({
3758
+ color: c,
3759
+ opacity: i === 0 ? 0.3 + bass * 0.7 : i === 1 ? 0.3 + mid * 0.7 : 0.3 + high * 0.7
3760
+ })),
3761
+ // Pulse glow - big and reactive
3762
+ pulseInset: 24 + bass * 50,
3763
+ pulseOpacity: 0.3 + bass * 0.7,
3764
+ pulseScale: 1 + bass * 0.4,
3765
+ // Extra glow ring
3766
+ ringOpacity: 0.2 + overall * 0.6,
3767
+ ringScale: 1 + overall * 0.3
3768
+ };
3769
+ }
3770
+ __name(calculateSpotlight, "calculateSpotlight");
3771
+ var EFFECT_ANIMATIONS = `
3772
+ @keyframes spotlight-spin {
3773
+ 0% { transform: rotate(0deg); }
3774
+ 100% { transform: rotate(360deg); }
3775
+ }
3776
+
3777
+ @keyframes orb-float-1 {
3778
+ 0%, 100% { transform: translate(-50%, -50%) translateY(0); }
3779
+ 50% { transform: translate(-50%, -50%) translateY(-15px); }
3780
+ }
3781
+
3782
+ @keyframes orb-float-2 {
3783
+ 0%, 100% { transform: translate(-50%, -50%) translateX(0); }
3784
+ 50% { transform: translate(-50%, -50%) translateX(15px); }
3785
+ }
3786
+
3787
+ @keyframes orb-float-3 {
3788
+ 0%, 100% { transform: translate(-50%, -50%) translate(0, 0); }
3789
+ 33% { transform: translate(-50%, -50%) translate(10px, -10px); }
3790
+ 66% { transform: translate(-50%, -50%) translate(-10px, 10px); }
3791
+ }
3792
+
3793
+ @keyframes orb-float-4 {
3794
+ 0%, 100% { transform: translate(-50%, -50%) translate(0, 0); }
3795
+ 50% { transform: translate(-50%, -50%) translate(-15px, -10px); }
3796
+ }
3797
+
3798
+ @keyframes mesh-float-1 {
3799
+ 0%, 100% { transform: translate(0, 0) scale(1); }
3800
+ 25% { transform: translate(-5%, 10%) scale(1.05); }
3801
+ 50% { transform: translate(5%, 5%) scale(0.95); }
3802
+ 75% { transform: translate(-3%, -5%) scale(1.02); }
3803
+ }
3804
+
3805
+ @keyframes mesh-float-2 {
3806
+ 0%, 100% { transform: translate(0, 0) scale(1); }
3807
+ 33% { transform: translate(8%, -8%) scale(1.08); }
3808
+ 66% { transform: translate(-6%, 6%) scale(0.92); }
3809
+ }
3810
+
3811
+ @keyframes mesh-float-3 {
3812
+ 0%, 100% { transform: translate(0, 0) scale(1); }
3813
+ 50% { transform: translate(10%, 10%) scale(1.1); }
3814
+ }
3815
+
3816
+ @keyframes mesh-float-4 {
3817
+ 0%, 100% { transform: translate(0, 0) scale(1) rotate(0deg); }
3818
+ 25% { transform: translate(10%, -5%) scale(1.1) rotate(5deg); }
3819
+ 50% { transform: translate(-5%, 10%) scale(0.95) rotate(-5deg); }
3820
+ 75% { transform: translate(-10%, -10%) scale(1.05) rotate(3deg); }
3821
+ }
3822
+
3823
+ @keyframes mesh-pulse {
3824
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
3825
+ 50% { transform: translate(-50%, -50%) scale(1.2); opacity: 0.5; }
3826
+ }
3827
+
3828
+ @keyframes glow-breathe {
3829
+ 0%, 100% { opacity: 0.6; transform: scale(1); }
3830
+ 50% { opacity: 1; transform: scale(1.05); }
3831
+ }
3832
+
3833
+ @keyframes glow-rotate {
3834
+ 0% { transform: rotate(0deg); }
3835
+ 100% { transform: rotate(360deg); }
3836
+ }
3837
+
3838
+ @keyframes sparkle-move {
3839
+ 0% { opacity: 0; transform: scale(0.8); }
3840
+ 50% { opacity: 1; }
3841
+ 100% { opacity: 0; transform: scale(1.2); }
3842
+ }
3843
+ `;
3844
+ function GlowEffect({ data, colors, isPlaying }) {
3845
+ const { layers, hueShift, showPulseRings, showSparkle } = data;
3846
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3847
+ layers.map((layer, i) => /* @__PURE__ */ jsx(
3848
+ "div",
3849
+ {
3850
+ className: cn("absolute rounded-2xl -z-10", layer.blur),
3851
+ style: {
3852
+ inset: `-${layer.inset}px`,
3853
+ background: layer.background,
3854
+ opacity: isPlaying ? layer.opacity : 0,
3855
+ transform: i < 2 ? `scaleY(${layer.scale})` : `scale(${layer.scale})`,
3856
+ animation: isPlaying && layer.animation ? layer.animation : "none",
3857
+ transition: "opacity 0.3s"
3858
+ }
3859
+ },
3860
+ i
3861
+ )),
3862
+ /* @__PURE__ */ jsx(
3863
+ "div",
3864
+ {
3865
+ className: "absolute rounded-2xl blur-xl overflow-hidden -z-10",
3866
+ style: {
3867
+ inset: "-75px",
3868
+ opacity: isPlaying ? 0.6 : 0,
3869
+ transition: "opacity 0.5s"
3870
+ },
3871
+ children: /* @__PURE__ */ jsx(
3872
+ "div",
3873
+ {
3874
+ className: "absolute inset-0",
3875
+ style: {
3876
+ background: `conic-gradient(
3877
+ from ${hueShift}deg at 50% 50%,
3878
+ hsl(${colors[0]} / 0.4) 0deg,
3879
+ hsl(${colors[1] || colors[0]} / 0.3) 90deg,
3880
+ hsl(${colors[2] || colors[0]} / 0.3) 180deg,
3881
+ hsl(${colors[3] || colors[0]} / 0.3) 270deg,
3882
+ hsl(${colors[0]} / 0.4) 360deg
3883
+ )`,
3884
+ animation: isPlaying ? "glow-rotate 6s linear infinite" : "none"
3885
+ }
3886
+ }
3887
+ )
3888
+ }
3889
+ ),
3890
+ showPulseRings && /* @__PURE__ */ jsxs(Fragment, { children: [
3891
+ /* @__PURE__ */ jsx(
3892
+ "div",
3893
+ {
3894
+ className: "absolute -inset-6 rounded-xl border-2 animate-ping -z-10",
3895
+ style: {
3896
+ borderColor: `hsl(${colors[0]} / 0.4)`,
3897
+ animationDuration: "1s"
3898
+ }
3899
+ }
3900
+ ),
3901
+ /* @__PURE__ */ jsx(
3902
+ "div",
3903
+ {
3904
+ className: "absolute -inset-12 rounded-2xl border animate-ping -z-10",
3905
+ style: {
3906
+ borderColor: `hsl(${colors[1] || colors[0]} / 0.3)`,
3907
+ animationDuration: "1.5s",
3908
+ animationDelay: "0.2s"
3909
+ }
3910
+ }
3911
+ )
3912
+ ] }),
3913
+ showSparkle && /* @__PURE__ */ jsx(
3914
+ "div",
3915
+ {
3916
+ className: "absolute -inset-18 rounded-3xl -z-10",
3917
+ style: {
3918
+ background: `radial-gradient(circle at 50% 30%, hsl(${colors[2] || colors[0]} / 0.5) 0%, transparent 30%)`,
3919
+ animation: "sparkle-move 0.5s ease-out"
3920
+ }
3921
+ }
3922
+ )
3923
+ ] });
3924
+ }
3925
+ __name(GlowEffect, "GlowEffect");
3926
+ function OrbsEffect({ orbs, blur, isPlaying }) {
3927
+ return /* @__PURE__ */ jsx(Fragment, { children: orbs.map((orb, i) => /* @__PURE__ */ jsx(
3928
+ "div",
3929
+ {
3930
+ className: cn("absolute rounded-full -z-10", blur),
3931
+ style: {
3932
+ width: orb.size,
3933
+ height: orb.size,
3934
+ left: `${orb.x}%`,
3935
+ top: `${orb.y}%`,
3936
+ background: `radial-gradient(circle at 30% 30%, hsl(${orb.color}) 0%, hsl(${orb.color} / 0.5) 40%, transparent 70%)`,
3937
+ opacity: isPlaying ? orb.opacity : 0,
3938
+ transform: `translate(-50%, -50%) scale(${orb.scale})`,
3939
+ transition: "all 0.08s ease-out"
3940
+ }
3941
+ },
3942
+ i
3943
+ )) });
3944
+ }
3945
+ __name(OrbsEffect, "OrbsEffect");
3946
+ function SpotlightEffect({ data, colors, blur, isPlaying }) {
3947
+ const inset = "inset" in data ? data.inset : 12;
3948
+ const pulseInset = "pulseInset" in data ? data.pulseInset : 24;
3949
+ const ringOpacity = "ringOpacity" in data ? data.ringOpacity : 0.3;
3950
+ const ringScale = "ringScale" in data ? data.ringScale : 1;
3951
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3952
+ /* @__PURE__ */ jsx(
3953
+ "div",
3954
+ {
3955
+ className: cn("absolute rounded-xl -z-10", blur),
3956
+ style: {
3957
+ inset: `-${inset}px`,
3958
+ background: `conic-gradient(
3959
+ from ${data.rotation}deg,
3960
+ hsl(${colors[0]} / ${data.colors[0]?.opacity || 0.5}),
3961
+ hsl(${colors[1] || colors[0]} / ${data.colors[1]?.opacity || 0.7}),
3962
+ hsl(${colors[2] || colors[0]} / ${data.colors[2]?.opacity || 0.5}),
3963
+ hsl(${colors[0]} / ${data.colors[1]?.opacity || 0.7}),
3964
+ hsl(${colors[0]} / ${data.colors[0]?.opacity || 0.5})
3965
+ )`,
3966
+ opacity: isPlaying ? 1 : 0,
3967
+ transition: "all 0.08s ease-out"
3968
+ }
3969
+ }
3970
+ ),
3971
+ /* @__PURE__ */ jsx(
3972
+ "div",
3973
+ {
3974
+ className: "absolute -inset-1 rounded-lg bg-background -z-10",
3975
+ style: { opacity: isPlaying ? 1 : 0, transition: "opacity 0.1s" }
3976
+ }
3977
+ ),
3978
+ /* @__PURE__ */ jsx(
3979
+ "div",
3980
+ {
3981
+ className: cn("absolute rounded-2xl -z-10", blur),
3982
+ style: {
3983
+ inset: `-${pulseInset}px`,
3984
+ background: `radial-gradient(circle, hsl(${colors[0]} / 0.7) 0%, hsl(${colors[0]} / 0.3) 50%, transparent 70%)`,
3985
+ opacity: isPlaying ? data.pulseOpacity : 0,
3986
+ transform: `scale(${data.pulseScale})`,
3987
+ transition: "all 0.08s ease-out"
3988
+ }
3989
+ }
3990
+ ),
3991
+ /* @__PURE__ */ jsx(
3992
+ "div",
3993
+ {
3994
+ className: "absolute rounded-3xl -z-10 blur-2xl",
3995
+ style: {
3996
+ inset: `-${pulseInset + 30}px`,
3997
+ background: `radial-gradient(circle, hsl(${colors[1] || colors[0]} / 0.4) 0%, transparent 60%)`,
3998
+ opacity: isPlaying ? ringOpacity : 0,
3999
+ transform: `scale(${ringScale})`,
4000
+ transition: "all 0.08s ease-out"
4001
+ }
4002
+ }
4003
+ )
4004
+ ] });
4005
+ }
4006
+ __name(SpotlightEffect, "SpotlightEffect");
4007
+ function MeshEffect({ gradients, isPlaying }) {
4008
+ return /* @__PURE__ */ jsx(Fragment, { children: gradients.map((g, i) => {
4009
+ const isCenter = "isCenter" in g && g.isCenter;
4010
+ const scale = "scale" in g ? g.scale : 1;
4011
+ const rotation = "rotation" in g ? g.rotation : 0;
4012
+ const itemBlur = "blur" in g ? g.blur : "blur-2xl";
4013
+ return /* @__PURE__ */ jsx(
4014
+ "div",
4015
+ {
4016
+ className: cn("absolute rounded-full -z-10", itemBlur),
4017
+ style: {
4018
+ width: g.width,
4019
+ height: g.height,
4020
+ top: "top" in g ? g.top : void 0,
4021
+ bottom: "bottom" in g ? g.bottom : void 0,
4022
+ left: "left" in g ? g.left : void 0,
4023
+ right: "right" in g ? g.right : void 0,
4024
+ background: isCenter ? `radial-gradient(circle, hsl(${g.color} / 0.6) 0%, hsl(${g.color} / 0.3) 30%, transparent 60%)` : `radial-gradient(circle, hsl(${g.color}) 0%, hsl(${g.color} / 0.5) 30%, transparent 65%)`,
4025
+ opacity: isPlaying ? g.opacity : 0,
4026
+ transform: isCenter ? `translate(-50%, -50%) scale(${scale})` : `scale(${scale}) rotate(${rotation}deg)`,
4027
+ transition: "all 0.08s ease-out"
4028
+ }
4029
+ },
4030
+ i
4031
+ );
4032
+ }) });
4033
+ }
4034
+ __name(MeshEffect, "MeshEffect");
4035
+ var SIZES = {
4036
+ sm: { container: "w-32 h-32", orbBase: 40 },
4037
+ md: { container: "w-40 h-40", orbBase: 50 },
4038
+ lg: { container: "w-48 h-48", orbBase: 60 }
4039
+ };
4040
+ function AudioReactiveCover({
4041
+ children,
4042
+ size = "lg",
4043
+ variant = "spotlight",
4044
+ intensity = "medium",
4045
+ colorScheme = "primary",
4046
+ onClick,
4047
+ className
4048
+ }) {
4049
+ const { isPlaying } = useHybridAudioState();
4050
+ const levels = useHybridAudioLevels();
4051
+ const sizeConfig = SIZES[size];
4052
+ const effectConfig = getEffectConfig(intensity);
4053
+ const { colors, hueShift } = prepareEffectColors(colorScheme, levels);
4054
+ const containerScale = 1 + levels.overall * effectConfig.scale;
4055
+ const glowData = variant === "glow" ? {
4056
+ layers: calculateGlowLayers(levels, effectConfig, colors),
4057
+ hueShift,
4058
+ showPulseRings: levels.bass > 0.5,
4059
+ showSparkle: levels.high > 0.4
4060
+ } : null;
4061
+ const orbsData = variant === "orbs" ? calculateOrbs(levels, effectConfig, colors, sizeConfig.orbBase) : null;
4062
+ const meshData = variant === "mesh" ? calculateMeshGradients(levels, effectConfig, colors) : null;
4063
+ const spotlightData = variant === "spotlight" ? calculateSpotlight(levels, effectConfig, colors, levels.mid * 360) : null;
4064
+ return /* @__PURE__ */ jsxs(
4065
+ "div",
4066
+ {
4067
+ className: cn("relative", sizeConfig.container, className),
4068
+ style: {
4069
+ transform: `scale(${containerScale})`,
4070
+ transition: "transform 0.1s ease-out"
4071
+ },
4072
+ children: [
4073
+ /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-0 pointer-events-none overflow-visible", children: [
4074
+ glowData && /* @__PURE__ */ jsx(GlowEffect, { data: glowData, colors, isPlaying }),
4075
+ orbsData && /* @__PURE__ */ jsx(OrbsEffect, { orbs: orbsData, blur: effectConfig.blur, isPlaying }),
4076
+ spotlightData && /* @__PURE__ */ jsx(SpotlightEffect, { data: spotlightData, colors, blur: effectConfig.blur, isPlaying }),
4077
+ meshData && /* @__PURE__ */ jsx(MeshEffect, { gradients: meshData, blur: effectConfig.blur, isPlaying })
4078
+ ] }),
4079
+ /* @__PURE__ */ jsx(
4080
+ "div",
4081
+ {
4082
+ className: "relative w-full h-full rounded-lg overflow-hidden shadow-2xl z-10 bg-background cursor-pointer",
4083
+ onClick,
4084
+ role: onClick ? "button" : void 0,
4085
+ tabIndex: onClick ? 0 : void 0,
4086
+ onKeyDown: onClick ? (e) => e.key === "Enter" && onClick() : void 0,
4087
+ children
4088
+ }
4089
+ ),
4090
+ /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EFFECT_ANIMATIONS } })
4091
+ ]
4092
+ }
4093
+ );
4094
+ }
4095
+ __name(AudioReactiveCover, "AudioReactiveCover");
4096
+ var COVER_SIZES = {
4097
+ sm: "w-24 h-24",
4098
+ md: "w-32 h-32",
4099
+ lg: "w-48 h-48"
4100
+ };
4101
+ function HybridSimplePlayer(props) {
4102
+ return /* @__PURE__ */ jsx(VisualizationProvider, { children: /* @__PURE__ */ jsx(HybridSimplePlayerContent, { ...props }) });
4103
+ }
4104
+ __name(HybridSimplePlayer, "HybridSimplePlayer");
4105
+ function HybridSimplePlayerContent({
4106
+ src,
4107
+ title,
4108
+ artist,
4109
+ coverArt,
4110
+ coverSize = "md",
4111
+ showWaveform = true,
4112
+ waveformMode = "frequency",
4113
+ waveformHeight = 64,
4114
+ showTimer = true,
4115
+ showVolume = true,
4116
+ showLoop = true,
4117
+ reactiveCover = true,
4118
+ variant,
4119
+ intensity,
4120
+ colorScheme,
4121
+ autoPlay = false,
4122
+ loop = false,
4123
+ initialVolume = 1,
4124
+ layout = "vertical",
4125
+ className,
4126
+ onPlay,
4127
+ onPause,
4128
+ onEnded,
4129
+ onError
4130
+ }) {
4131
+ const { settings: vizSettings, nextVariant } = useVisualization();
4132
+ const effectiveVariant = variant ?? (vizSettings.variant !== "none" ? vizSettings.variant : "spotlight");
4133
+ const effectiveIntensity = intensity ?? vizSettings.intensity;
4134
+ const effectiveColorScheme = colorScheme ?? vizSettings.colorScheme;
4135
+ const showReactiveCover = reactiveCover && effectiveVariant !== "none";
4136
+ const renderCoverContent = /* @__PURE__ */ __name(() => {
4137
+ if (typeof coverArt === "string") {
4138
+ return /* @__PURE__ */ jsx("img", { src: coverArt, alt: title || "Album cover", className: "w-full h-full object-cover" });
4139
+ }
4140
+ if (coverArt) {
4141
+ return coverArt;
4142
+ }
4143
+ return /* @__PURE__ */ jsx("div", { className: "w-full h-full bg-muted/30 flex items-center justify-center", children: /* @__PURE__ */ jsx(Music, { className: "w-1/3 h-1/3 text-muted-foreground/50" }) });
4144
+ }, "renderCoverContent");
4145
+ const isHorizontal = layout === "horizontal";
4146
+ return /* @__PURE__ */ jsx(
4147
+ HybridAudioProvider,
4148
+ {
4149
+ src,
4150
+ autoPlay,
4151
+ loop,
4152
+ initialVolume,
4153
+ onPlay,
4154
+ onPause,
4155
+ onEnded,
4156
+ onError,
4157
+ children: /* @__PURE__ */ jsxs(
4158
+ "div",
4159
+ {
4160
+ className: cn$1(
4161
+ "flex gap-4",
4162
+ isHorizontal ? "flex-row items-center" : "flex-col items-center",
4163
+ className
4164
+ ),
4165
+ children: [
4166
+ (coverArt || reactiveCover) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 shrink-0", children: [
4167
+ showReactiveCover ? /* @__PURE__ */ jsx(
4168
+ AudioReactiveCover,
4169
+ {
4170
+ size: coverSize,
4171
+ variant: effectiveVariant,
4172
+ intensity: effectiveIntensity,
4173
+ colorScheme: effectiveColorScheme,
4174
+ onClick: nextVariant,
4175
+ children: /* @__PURE__ */ jsx("div", { className: cn$1("rounded-lg overflow-hidden", COVER_SIZES[coverSize]), children: renderCoverContent() })
4176
+ }
4177
+ ) : /* @__PURE__ */ jsx(
4178
+ "div",
4179
+ {
4180
+ className: cn$1(
4181
+ "rounded-lg overflow-hidden shadow-lg cursor-pointer",
4182
+ COVER_SIZES[coverSize]
4183
+ ),
4184
+ onClick: nextVariant,
4185
+ role: "button",
4186
+ tabIndex: 0,
4187
+ onKeyDown: (e) => e.key === "Enter" && nextVariant(),
4188
+ children: renderCoverContent()
4189
+ }
4190
+ ),
4191
+ reactiveCover && /* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wider text-muted-foreground/50 select-none", children: vizSettings.variant === "none" ? "off" : vizSettings.variant })
4192
+ ] }),
4193
+ /* @__PURE__ */ jsxs(
4194
+ "div",
4195
+ {
4196
+ className: cn$1("flex flex-col gap-3", isHorizontal ? "flex-1 min-w-0" : "w-full max-w-md"),
4197
+ children: [
4198
+ (title || artist) && /* @__PURE__ */ jsxs("div", { className: cn$1("text-center", isHorizontal && "text-left"), children: [
4199
+ title && /* @__PURE__ */ jsx("h3", { className: "text-base font-medium text-foreground truncate", children: title }),
4200
+ artist && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground truncate", children: artist })
4201
+ ] }),
4202
+ /* @__PURE__ */ jsx(
4203
+ HybridAudioPlayer,
4204
+ {
4205
+ showControls: true,
4206
+ showWaveform,
4207
+ waveformMode,
4208
+ waveformHeight,
4209
+ showTimer,
4210
+ showVolume,
4211
+ showLoop,
4212
+ className: "border-0 bg-transparent"
4213
+ }
4214
+ )
4215
+ ]
4216
+ }
4217
+ )
4218
+ ]
4219
+ }
4220
+ )
4221
+ }
4222
+ );
4223
+ }
4224
+ __name(HybridSimplePlayerContent, "HybridSimplePlayerContent");
4225
+
4226
+ // src/tools/ImageViewer/utils/constants.ts
4227
+ var MAX_IMAGE_SIZE = 50 * 1024 * 1024;
4228
+ var WARNING_IMAGE_SIZE = 10 * 1024 * 1024;
4229
+ var PROGRESSIVE_LOADING_THRESHOLD = 500 * 1024;
4230
+ var LQIP_SIZE = 32;
4231
+ var LQIP_QUALITY = 0.5;
4232
+ var ZOOM_PRESETS = [
4233
+ { label: "Fit", value: "fit" },
4234
+ { label: "25%", value: 0.25 },
4235
+ { label: "50%", value: 0.5 },
4236
+ { label: "100%", value: 1 },
4237
+ { label: "200%", value: 2 },
4238
+ { label: "400%", value: 4 }
4239
+ ];
4240
+ var DEFAULT_TRANSFORM = {
4241
+ rotation: 0,
4242
+ flipH: false,
4243
+ flipV: false
4244
+ };
4245
+
4246
+ // src/tools/ImageViewer/utils/lqip.ts
4247
+ async function createLQIP(imageSrc) {
4248
+ try {
4249
+ const img = new Image();
4250
+ img.crossOrigin = "anonymous";
4251
+ await new Promise((resolve, reject) => {
4252
+ img.onload = () => resolve();
4253
+ img.onerror = () => reject(new Error("Failed to load image for LQIP"));
4254
+ img.src = imageSrc;
4255
+ });
4256
+ const aspect = img.naturalWidth / img.naturalHeight;
4257
+ const width = aspect >= 1 ? LQIP_SIZE : Math.round(LQIP_SIZE * aspect);
4258
+ const height = aspect >= 1 ? Math.round(LQIP_SIZE / aspect) : LQIP_SIZE;
4259
+ const canvas = document.createElement("canvas");
4260
+ canvas.width = width;
4261
+ canvas.height = height;
4262
+ const ctx = canvas.getContext("2d");
4263
+ if (!ctx) return null;
4264
+ ctx.drawImage(img, 0, 0, width, height);
4265
+ return canvas.toDataURL("image/jpeg", LQIP_QUALITY);
4266
+ } catch {
4267
+ return null;
4268
+ }
4269
+ }
4270
+ __name(createLQIP, "createLQIP");
4271
+ var imageDebug = createMediaLogger("ImageViewer");
4272
+ function ImageToolbar({
4273
+ scale,
4274
+ transform,
4275
+ onRotate,
4276
+ onFlipH,
4277
+ onFlipV,
4278
+ onZoomPreset,
4279
+ onExpand
4280
+ }) {
4281
+ const { zoomIn, zoomOut, resetTransform } = useControls();
4282
+ const zoomLabel = useMemo(() => {
4283
+ const percent = Math.round(scale * 100);
4284
+ return `${percent}%`;
4285
+ }, [scale]);
4286
+ return /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-0.5 bg-background/90 backdrop-blur-sm border rounded-lg p-1 shadow-lg", children: [
4287
+ /* @__PURE__ */ jsx(
4288
+ Button,
4289
+ {
4290
+ variant: "ghost",
4291
+ size: "icon",
4292
+ className: "h-7 w-7",
4293
+ onClick: () => zoomOut(),
4294
+ title: "Zoom out",
4295
+ children: /* @__PURE__ */ jsx(ZoomOut, { className: "h-3.5 w-3.5" })
4296
+ }
4297
+ ),
4298
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
4299
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", className: "h-7 px-2 min-w-[52px] font-mono text-xs", children: zoomLabel }) }),
4300
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "center", className: "min-w-[80px]", children: ZOOM_PRESETS.map((preset) => /* @__PURE__ */ jsx(
4301
+ DropdownMenuItem,
4302
+ {
4303
+ onClick: () => onZoomPreset(preset.value),
4304
+ className: "text-xs justify-center",
4305
+ children: preset.label
4306
+ },
4307
+ preset.label
4308
+ )) })
4309
+ ] }),
4310
+ /* @__PURE__ */ jsx(
4311
+ Button,
4312
+ {
4313
+ variant: "ghost",
4314
+ size: "icon",
4315
+ className: "h-7 w-7",
4316
+ onClick: () => zoomIn(),
4317
+ title: "Zoom in",
4318
+ children: /* @__PURE__ */ jsx(ZoomIn, { className: "h-3.5 w-3.5" })
4319
+ }
4320
+ ),
4321
+ /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-border mx-1" }),
4322
+ /* @__PURE__ */ jsx(
4323
+ Button,
4324
+ {
4325
+ variant: "ghost",
4326
+ size: "icon",
4327
+ className: "h-7 w-7",
4328
+ onClick: () => resetTransform(),
4329
+ title: "Fit to view",
4330
+ children: /* @__PURE__ */ jsx(Maximize2, { className: "h-3.5 w-3.5" })
4331
+ }
4332
+ ),
4333
+ /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-border mx-1" }),
4334
+ /* @__PURE__ */ jsx(
4335
+ Button,
4336
+ {
4337
+ variant: "ghost",
4338
+ size: "icon",
4339
+ className: cn("h-7 w-7", transform.flipH && "bg-accent"),
4340
+ onClick: onFlipH,
4341
+ title: "Flip horizontal",
4342
+ children: /* @__PURE__ */ jsx(FlipHorizontal, { className: "h-3.5 w-3.5" })
4343
+ }
4344
+ ),
4345
+ /* @__PURE__ */ jsx(
4346
+ Button,
4347
+ {
4348
+ variant: "ghost",
4349
+ size: "icon",
4350
+ className: cn("h-7 w-7", transform.flipV && "bg-accent"),
4351
+ onClick: onFlipV,
4352
+ title: "Flip vertical",
4353
+ children: /* @__PURE__ */ jsx(FlipVertical, { className: "h-3.5 w-3.5" })
4354
+ }
4355
+ ),
4356
+ /* @__PURE__ */ jsx(
4357
+ Button,
4358
+ {
4359
+ variant: "ghost",
4360
+ size: "icon",
4361
+ className: "h-7 w-7",
4362
+ onClick: onRotate,
4363
+ title: "Rotate 90\xB0",
4364
+ children: /* @__PURE__ */ jsx(RotateCw, { className: "h-3.5 w-3.5" })
4365
+ }
4366
+ ),
4367
+ transform.rotation !== 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground font-mono pl-1", children: [
4368
+ transform.rotation,
4369
+ "\xB0"
4370
+ ] }),
4371
+ onExpand && /* @__PURE__ */ jsxs(Fragment, { children: [
4372
+ /* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-border mx-1" }),
4373
+ /* @__PURE__ */ jsx(
4374
+ Button,
4375
+ {
4376
+ variant: "ghost",
4377
+ size: "icon",
4378
+ className: "h-7 w-7",
4379
+ onClick: onExpand,
4380
+ title: "Open in fullscreen",
4381
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" })
4382
+ }
4383
+ )
4384
+ ] })
4385
+ ] });
4386
+ }
4387
+ __name(ImageToolbar, "ImageToolbar");
4388
+ function ImageInfo({ src }) {
4389
+ const { getDimensions, cacheDimensions } = useImageCache();
4390
+ const [dimensions, setDimensions] = useState(null);
4391
+ useEffect(() => {
4392
+ const cached = getDimensions(src);
4393
+ if (cached) {
4394
+ setDimensions({ w: cached.width, h: cached.height });
4395
+ return;
4396
+ }
4397
+ const img = new Image();
4398
+ img.onload = () => {
4399
+ const dims = { w: img.naturalWidth, h: img.naturalHeight };
4400
+ setDimensions(dims);
4401
+ cacheDimensions(src, { width: dims.w, height: dims.h });
4402
+ };
4403
+ img.src = src;
4404
+ }, [src, getDimensions, cacheDimensions]);
4405
+ if (!dimensions) return null;
4406
+ return /* @__PURE__ */ jsxs("div", { className: "absolute top-3 right-3 z-10 px-2 py-1 bg-background/80 backdrop-blur-sm border rounded text-[10px] text-muted-foreground font-mono", children: [
4407
+ dimensions.w,
4408
+ " \xD7 ",
4409
+ dimensions.h
4410
+ ] });
4411
+ }
4412
+ __name(ImageInfo, "ImageInfo");
4413
+ function useImageTransform(options = {}) {
4414
+ const { resetKey } = options;
4415
+ const [transform, setTransform] = useState(DEFAULT_TRANSFORM);
4416
+ useEffect(() => {
4417
+ setTransform(DEFAULT_TRANSFORM);
4418
+ }, [resetKey]);
4419
+ const rotate = useCallback(() => {
4420
+ setTransform((prev) => ({
4421
+ ...prev,
4422
+ rotation: (prev.rotation + 90) % 360
4423
+ }));
4424
+ }, []);
4425
+ const flipH = useCallback(() => {
4426
+ setTransform((prev) => ({
4427
+ ...prev,
4428
+ flipH: !prev.flipH
4429
+ }));
4430
+ }, []);
4431
+ const flipV = useCallback(() => {
4432
+ setTransform((prev) => ({
4433
+ ...prev,
4434
+ flipV: !prev.flipV
4435
+ }));
4436
+ }, []);
4437
+ const reset = useCallback(() => {
4438
+ setTransform(DEFAULT_TRANSFORM);
4439
+ }, []);
4440
+ const transformStyle = useMemo(() => {
4441
+ const transforms = [];
4442
+ if (transform.rotation !== 0) {
4443
+ transforms.push(`rotate(${transform.rotation}deg)`);
4444
+ }
4445
+ if (transform.flipH) {
4446
+ transforms.push("scaleX(-1)");
4447
+ }
4448
+ if (transform.flipV) {
4449
+ transforms.push("scaleY(-1)");
4450
+ }
4451
+ return transforms.join(" ") || "none";
4452
+ }, [transform]);
4453
+ return {
4454
+ transform,
4455
+ rotate,
4456
+ flipH,
4457
+ flipV,
4458
+ reset,
4459
+ transformStyle
4460
+ };
4461
+ }
4462
+ __name(useImageTransform, "useImageTransform");
4463
+ function useImageLoading(options) {
4464
+ const { content, mimeType, src: directSrc } = options;
4465
+ const getOrCreateBlobUrl = useMediaCacheStore.getState().getOrCreateBlobUrl;
4466
+ const releaseBlobUrl = useMediaCacheStore.getState().releaseBlobUrl;
4467
+ const [src, setSrc] = useState(null);
4468
+ const [lqip, setLqip] = useState(null);
4469
+ const [isFullyLoaded, setIsFullyLoaded] = useState(false);
4470
+ const [error, setError] = useState(null);
4471
+ const contentKeyRef = useRef(null);
4472
+ const isMountedRef = useRef(true);
4473
+ const size = content ? typeof content === "string" ? content.length : content.byteLength : 0;
4474
+ const hasContent = directSrc ? true : size > 0;
4475
+ const useProgressiveLoading = directSrc ? false : size > PROGRESSIVE_LOADING_THRESHOLD;
4476
+ useEffect(() => {
4477
+ isMountedRef.current = true;
4478
+ return () => {
4479
+ isMountedRef.current = false;
4480
+ if (contentKeyRef.current) {
4481
+ useMediaCacheStore.getState().releaseBlobUrl(contentKeyRef.current);
4482
+ contentKeyRef.current = null;
4483
+ }
4484
+ };
4485
+ }, []);
4486
+ useEffect(() => {
4487
+ setError(null);
4488
+ if (directSrc) {
4489
+ imageDebug.load(directSrc, "url");
4490
+ setSrc(directSrc);
4491
+ setIsFullyLoaded(true);
4492
+ return;
4493
+ }
4494
+ if (!hasContent) {
4495
+ setSrc(null);
4496
+ return;
4497
+ }
4498
+ if (size > MAX_IMAGE_SIZE) {
4499
+ const sizeMB = (size / 1024 / 1024).toFixed(1);
4500
+ const errorMsg = `Image too large: ${sizeMB}MB (maximum: 50MB)`;
4501
+ imageDebug.error(errorMsg, { size, sizeMB, maxSize: MAX_IMAGE_SIZE });
4502
+ setError(errorMsg);
4503
+ setSrc(null);
4504
+ return;
4505
+ }
4506
+ if (size > WARNING_IMAGE_SIZE) {
4507
+ const sizeMB = (size / 1024 / 1024).toFixed(1);
4508
+ imageDebug.warn(`Large image: ${sizeMB}MB - may impact performance`);
4509
+ }
4510
+ if (typeof content === "string") {
4511
+ if (content.startsWith("data:")) {
4512
+ imageDebug.load(content.slice(0, 50) + "...", "data-url");
4513
+ setSrc(content);
4514
+ return;
4515
+ }
4516
+ const encoder = new TextEncoder();
4517
+ const buffer = encoder.encode(content).buffer;
4518
+ const contentKey2 = generateContentKey(buffer);
4519
+ if (contentKeyRef.current && contentKeyRef.current !== contentKey2) {
4520
+ releaseBlobUrl(contentKeyRef.current);
4521
+ }
4522
+ contentKeyRef.current = contentKey2;
4523
+ const url2 = getOrCreateBlobUrl(contentKey2, buffer, mimeType || "image/png");
4524
+ imageDebug.load(url2, "blob");
4525
+ imageDebug.state("loaded", { size, mimeType, contentKey: contentKey2 });
4526
+ setSrc(url2);
4527
+ return;
4528
+ }
4529
+ const contentKey = generateContentKey(content);
4530
+ if (contentKeyRef.current && contentKeyRef.current !== contentKey) {
4531
+ releaseBlobUrl(contentKeyRef.current);
4532
+ }
4533
+ contentKeyRef.current = contentKey;
4534
+ const url = getOrCreateBlobUrl(contentKey, content, mimeType || "image/png");
4535
+ imageDebug.load(url, "blob");
4536
+ imageDebug.state("loaded", { size, mimeType, contentKey });
4537
+ setSrc(url);
4538
+ }, [content, mimeType, hasContent, size, directSrc]);
4539
+ useEffect(() => {
4540
+ if (!src || !useProgressiveLoading) {
4541
+ setLqip(null);
4542
+ setIsFullyLoaded(true);
4543
+ return;
4544
+ }
4545
+ setIsFullyLoaded(false);
4546
+ imageDebug.state("progressive loading", { size });
4547
+ createLQIP(src).then((placeholder) => {
4548
+ if (placeholder) {
4549
+ imageDebug.debug("LQIP created");
4550
+ setLqip(placeholder);
4551
+ }
4552
+ });
4553
+ const img = new Image();
4554
+ img.onload = () => {
4555
+ imageDebug.state("fully loaded");
4556
+ setIsFullyLoaded(true);
4557
+ };
4558
+ img.onerror = () => {
4559
+ imageDebug.error("Failed to load full image");
4560
+ };
4561
+ img.src = src;
4562
+ }, [src, useProgressiveLoading, size]);
4563
+ return {
4564
+ src,
4565
+ lqip,
4566
+ isFullyLoaded,
4567
+ useProgressiveLoading,
4568
+ error,
4569
+ contentKey: contentKeyRef.current,
4570
+ size,
4571
+ hasContent
4572
+ };
4573
+ }
4574
+ __name(useImageLoading, "useImageLoading");
4575
+ function ImageViewer({ file, content, src: directSrc, inDialog = false }) {
4576
+ const [scale, setScale] = useState(1);
4577
+ const [dialogOpen, setDialogOpen] = useState(false);
4578
+ const [loadError, setLoadError] = useState(false);
4579
+ const containerRef = useRef(null);
4580
+ const controlsRef = useRef(null);
4581
+ const {
4582
+ src,
4583
+ lqip,
4584
+ isFullyLoaded,
4585
+ useProgressiveLoading,
4586
+ error,
4587
+ hasContent
4588
+ } = useImageLoading({ content, mimeType: file.mimeType, src: directSrc });
4589
+ useEffect(() => {
4590
+ setLoadError(false);
4591
+ }, [src]);
4592
+ const { transform, rotate, flipH, flipV, transformStyle } = useImageTransform({
4593
+ resetKey: file.path
4594
+ });
4595
+ const handleZoomPreset = useCallback((value) => {
4596
+ if (!controlsRef.current) return;
4597
+ if (value === "fit") {
4598
+ controlsRef.current.resetTransform();
4599
+ } else {
4600
+ controlsRef.current.setTransform(0, 0, value);
4601
+ }
4602
+ }, []);
4603
+ const handleExpand = useCallback(() => {
4604
+ setDialogOpen(true);
4605
+ }, []);
4606
+ useEffect(() => {
4607
+ const handleKeyDown = /* @__PURE__ */ __name((e) => {
4608
+ if (!containerRef.current?.contains(document.activeElement) && document.activeElement !== containerRef.current) {
4609
+ return;
4610
+ }
4611
+ const controls = controlsRef.current;
4612
+ if (!controls) return;
4613
+ switch (e.key) {
4614
+ case "+":
4615
+ case "=":
4616
+ e.preventDefault();
4617
+ controls.zoomIn();
4618
+ break;
4619
+ case "-":
4620
+ e.preventDefault();
4621
+ controls.zoomOut();
4622
+ break;
4623
+ case "0":
4624
+ e.preventDefault();
4625
+ controls.resetTransform();
4626
+ break;
4627
+ case "r":
4628
+ if (!e.metaKey && !e.ctrlKey) {
4629
+ e.preventDefault();
4630
+ rotate();
4631
+ }
4632
+ break;
4633
+ }
4634
+ }, "handleKeyDown");
4635
+ window.addEventListener("keydown", handleKeyDown);
4636
+ return () => window.removeEventListener("keydown", handleKeyDown);
4637
+ }, [rotate]);
4638
+ if (error || loadError) {
4639
+ return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-3 bg-muted/30 p-4", children: [
4640
+ /* @__PURE__ */ jsx(AlertCircle, { className: "w-12 h-12 text-destructive/70" }),
4641
+ /* @__PURE__ */ jsxs(Alert$1, { variant: "destructive", className: "max-w-md", children: [
4642
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
4643
+ /* @__PURE__ */ jsx(AlertDescription$1, { children: error || "Failed to load image" })
4644
+ ] })
4645
+ ] });
4646
+ }
4647
+ if (!hasContent) {
4648
+ return /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30", children: [
4649
+ /* @__PURE__ */ jsx(ImageIcon, { className: "w-12 h-12 text-muted-foreground/50" }),
4650
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No image content" })
4651
+ ] });
4652
+ }
4653
+ return /* @__PURE__ */ jsxs(
4654
+ "div",
4655
+ {
4656
+ ref: containerRef,
4657
+ tabIndex: 0,
4658
+ className: cn$1(
4659
+ "flex-1 h-full relative overflow-hidden outline-none",
4660
+ "bg-[length:16px_16px]",
4661
+ "[background-color:hsl(var(--muted)/0.2)]",
4662
+ "[background-image:linear-gradient(45deg,hsl(var(--muted)/0.4)_25%,transparent_25%),linear-gradient(-45deg,hsl(var(--muted)/0.4)_25%,transparent_25%),linear-gradient(45deg,transparent_75%,hsl(var(--muted)/0.4)_75%),linear-gradient(-45deg,transparent_75%,hsl(var(--muted)/0.4)_75%)]",
4663
+ "[background-position:0_0,0_8px,8px_-8px,-8px_0px]"
4664
+ ),
4665
+ children: [
4666
+ src && /* @__PURE__ */ jsx(ImageInfo, { src }),
4667
+ useProgressiveLoading && !isFullyLoaded && /* @__PURE__ */ jsxs("div", { className: "absolute top-3 left-3 z-10 px-2 py-1 bg-background/80 backdrop-blur-sm border rounded text-[10px] text-muted-foreground font-mono flex items-center gap-1.5", children: [
4668
+ /* @__PURE__ */ jsx("div", { className: "w-2 h-2 bg-blue-500 rounded-full animate-pulse" }),
4669
+ "Loading..."
4670
+ ] }),
4671
+ /* @__PURE__ */ jsxs(
4672
+ TransformWrapper,
4673
+ {
4674
+ initialScale: 1,
4675
+ minScale: 0.1,
4676
+ maxScale: 8,
4677
+ centerOnInit: true,
4678
+ centerZoomedOut: true,
4679
+ onTransformed: (ref, state) => {
4680
+ setScale(state.scale);
4681
+ controlsRef.current = ref;
4682
+ },
4683
+ onInit: (ref) => {
4684
+ controlsRef.current = ref;
4685
+ },
4686
+ wheel: { step: 0.1 },
4687
+ doubleClick: { mode: "toggle", step: 2 },
4688
+ panning: { velocityDisabled: false },
4689
+ children: [
4690
+ /* @__PURE__ */ jsx(
4691
+ ImageToolbar,
4692
+ {
4693
+ scale,
4694
+ transform,
4695
+ onRotate: rotate,
4696
+ onFlipH: flipH,
4697
+ onFlipV: flipV,
4698
+ onZoomPreset: handleZoomPreset,
4699
+ onExpand: !inDialog ? handleExpand : void 0
4700
+ }
4701
+ ),
4702
+ /* @__PURE__ */ jsx(
4703
+ TransformComponent,
4704
+ {
4705
+ wrapperClass: "!w-full !h-full cursor-grab active:cursor-grabbing",
4706
+ contentClass: "!w-full !h-full flex items-center justify-center",
4707
+ children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4708
+ useProgressiveLoading && lqip && !isFullyLoaded && /* @__PURE__ */ jsx(
4709
+ "img",
4710
+ {
4711
+ src: lqip,
4712
+ alt: "",
4713
+ "aria-hidden": "true",
4714
+ className: "absolute inset-0 max-w-full max-h-full object-contain select-none",
4715
+ style: {
4716
+ transform: transformStyle,
4717
+ filter: "blur(20px)",
4718
+ transition: "opacity 0.3s ease-out",
4719
+ opacity: isFullyLoaded ? 0 : 1
4720
+ },
4721
+ draggable: false
4722
+ }
4723
+ ),
4724
+ src && /* @__PURE__ */ jsx(
4725
+ "img",
4726
+ {
4727
+ src,
4728
+ alt: file.name,
4729
+ className: "max-w-full max-h-full object-contain select-none",
4730
+ style: {
4731
+ transform: transformStyle,
4732
+ transition: useProgressiveLoading ? "transform 0.15s ease-out, opacity 0.3s ease-out" : "transform 0.15s ease-out",
4733
+ opacity: useProgressiveLoading && !isFullyLoaded ? 0 : 1
4734
+ },
4735
+ draggable: false,
4736
+ crossOrigin: "anonymous",
4737
+ onError: () => setLoadError(true)
4738
+ }
4739
+ )
4740
+ ] })
4741
+ }
4742
+ )
4743
+ ]
4744
+ }
4745
+ ),
4746
+ !inDialog && /* @__PURE__ */ jsx(Dialog, { open: dialogOpen, onOpenChange: setDialogOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-w-[95vw] max-h-[95vh] w-[95vw] h-[95vh] p-0 overflow-hidden [&>button]:hidden flex flex-col", children: [
4747
+ /* @__PURE__ */ jsx(DialogTitle, { className: "sr-only", children: file.name }),
4748
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-4 py-2 border-b shrink-0", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: file.name }) }),
4749
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(ImageViewer, { file, content, src: directSrc, inDialog: true }) })
4750
+ ] }) })
4751
+ ]
4752
+ }
4753
+ );
4754
+ }
4755
+ __name(ImageViewer, "ImageViewer");
4756
+ var extractTextFromChildren = /* @__PURE__ */ __name((children) => {
4757
+ if (typeof children === "string") {
4758
+ return children;
4759
+ }
4760
+ if (typeof children === "number") {
4761
+ return String(children);
4762
+ }
4763
+ if (React17.isValidElement(children)) {
4764
+ const props = children.props;
4765
+ return extractTextFromChildren(props.children);
4766
+ }
4767
+ if (Array.isArray(children)) {
4768
+ return children.map(extractTextFromChildren).join("");
4769
+ }
4770
+ return "";
4771
+ }, "extractTextFromChildren");
4772
+ var CodeBlock = /* @__PURE__ */ __name(({ code, language, isUser, isCompact = false }) => {
4773
+ const theme = useResolvedTheme();
4774
+ return /* @__PURE__ */ jsxs("div", { className: "relative group my-3", children: [
4775
+ /* @__PURE__ */ jsx(
4776
+ CopyButton,
4777
+ {
4778
+ value: code,
4779
+ variant: "ghost",
4780
+ className: `
4781
+ absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity
4782
+ h-8 w-8
4783
+ ${isUser ? "hover:bg-white/20 text-white" : "hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground"}
4784
+ `,
4785
+ title: "Copy code"
4786
+ }
4787
+ ),
4788
+ /* @__PURE__ */ jsx(
4789
+ PrettyCode_default,
4790
+ {
4791
+ data: code,
4792
+ language,
4793
+ className: isCompact ? "text-xs" : "text-sm",
4794
+ customBg: isUser ? "bg-white/10" : "bg-muted dark:bg-muted",
4795
+ mode: theme,
4796
+ isCompact
4797
+ }
4798
+ )
4799
+ ] });
4800
+ }, "CodeBlock");
4801
+ var createMarkdownComponents = /* @__PURE__ */ __name((isUser = false, isCompact = false) => {
4802
+ const textSize = isCompact ? "text-xs" : "text-sm";
4803
+ const headingBase = isCompact ? "text-sm" : "text-base";
4804
+ const headingSm = isCompact ? "text-xs" : "text-sm";
4805
+ return {
4806
+ // Headings - scaled for chat context
4807
+ h1: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("h1", { className: `${headingBase} font-bold mb-2 mt-3 first:mt-0`, children }), "h1"),
4808
+ h2: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("h2", { className: `${headingSm} font-bold mb-2 mt-3 first:mt-0`, children }), "h2"),
4809
+ h3: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("h3", { className: `${headingSm} font-semibold mb-1 mt-2 first:mt-0`, children }), "h3"),
4810
+ h4: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("h4", { className: `${headingSm} font-semibold mb-1 mt-2 first:mt-0`, children }), "h4"),
4811
+ h5: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("h5", { className: `${headingSm} font-medium mb-1 mt-2 first:mt-0`, children }), "h5"),
4812
+ h6: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("h6", { className: `${headingSm} font-medium mb-1 mt-2 first:mt-0`, children }), "h6"),
4813
+ // Paragraphs - compact spacing for chat
4814
+ p: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("p", { className: `${textSize} mb-2 last:mb-0 leading-relaxed break-words`, children }), "p"),
4815
+ // Lists - compact
4816
+ ul: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("ul", { className: `list-disc list-inside mb-2 space-y-1 ${textSize}`, children }), "ul"),
4817
+ ol: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("ol", { className: `list-decimal list-inside mb-2 space-y-1 ${textSize}`, children }), "ol"),
4818
+ li: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("li", { className: "break-words", children }), "li"),
4819
+ // Links - appropriate for chat context
4820
+ a: /* @__PURE__ */ __name(({ href, children }) => /* @__PURE__ */ jsx(
4821
+ "a",
4822
+ {
4823
+ href,
4824
+ className: `${textSize} text-primary underline hover:text-primary/80 transition-colors break-all`,
4825
+ target: href?.startsWith("http") ? "_blank" : void 0,
4826
+ rel: href?.startsWith("http") ? "noopener noreferrer" : void 0,
4827
+ children
4828
+ }
4829
+ ), "a"),
4830
+ // Code blocks - using CodeBlock component with copy functionality
4831
+ pre: /* @__PURE__ */ __name(({ children }) => {
4832
+ let codeContent = "";
4833
+ let language = "plaintext";
4834
+ if (React17.isValidElement(children)) {
4835
+ const child = children;
4836
+ if (child.type === "code" || typeof child.type === "function" && child.type.name === "code") {
4837
+ const codeProps = child.props;
4838
+ const rawClassName = codeProps.className;
4839
+ language = rawClassName?.replace(/language-/, "").trim() || "plaintext";
4840
+ codeContent = extractTextFromChildren(codeProps.children).trim();
4841
+ } else {
4842
+ codeContent = extractTextFromChildren(children).trim();
4843
+ }
4844
+ } else {
4845
+ codeContent = extractTextFromChildren(children).trim();
4846
+ }
4847
+ if (!codeContent) {
4848
+ return /* @__PURE__ */ jsx("div", { className: "my-3 p-3 bg-muted rounded text-sm text-muted-foreground", children: "No content available" });
4849
+ }
4850
+ if (language === "mermaid") {
4851
+ return /* @__PURE__ */ jsx("div", { className: "my-3 max-w-full overflow-x-auto", children: /* @__PURE__ */ jsx(Mermaid_default, { chart: codeContent, className: "max-w-[600px] mx-auto", isCompact }) });
4852
+ }
4853
+ try {
4854
+ return /* @__PURE__ */ jsx(CodeBlock, { code: codeContent, language, isUser, isCompact });
4855
+ } catch (error) {
4856
+ console.warn("CodeBlock failed, using fallback:", error);
4857
+ return /* @__PURE__ */ jsxs("div", { className: "relative group my-3", children: [
4858
+ /* @__PURE__ */ jsx(
4859
+ CopyButton,
4860
+ {
4861
+ value: codeContent,
4862
+ variant: "ghost",
4863
+ className: `
4864
+ absolute top-2 right-2 z-10 opacity-0 group-hover:opacity-100 transition-opacity
4865
+ h-8 w-8
4866
+ ${isUser ? "hover:bg-white/20 text-white" : "hover:bg-muted-foreground/20 text-muted-foreground hover:text-foreground"}
4867
+ `,
4868
+ title: "Copy code"
4869
+ }
4870
+ ),
4871
+ /* @__PURE__ */ jsx("pre", { className: `
4872
+ p-3 rounded text-xs font-mono overflow-x-auto
4873
+ ${isUser ? "bg-white/10 text-white" : "bg-muted text-foreground"}
4874
+ `, children: /* @__PURE__ */ jsx("code", { children: codeContent }) })
4875
+ ] });
4876
+ }
4877
+ }, "pre"),
4878
+ // Inline code
4879
+ code: /* @__PURE__ */ __name(({ children, className }) => {
4880
+ if (className?.includes("language-")) {
4881
+ return /* @__PURE__ */ jsx("code", { className, children });
4882
+ }
4883
+ const codeContent = extractTextFromChildren(children);
4884
+ return /* @__PURE__ */ jsx("code", { className: "px-1.5 py-0.5 rounded text-xs font-mono bg-muted text-foreground break-all", children: codeContent });
4885
+ }, "code"),
4886
+ // Blockquotes
4887
+ blockquote: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("blockquote", { className: `${textSize} border-l-2 border-border pl-3 my-2 italic text-muted-foreground break-words`, children }), "blockquote"),
4888
+ // Tables - compact for chat
4889
+ table: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("div", { className: "overflow-x-auto my-3", children: /* @__PURE__ */ jsx("table", { className: `min-w-full ${textSize} border-collapse`, children }) }), "table"),
4890
+ thead: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("thead", { className: "bg-muted/50", children }), "thead"),
4891
+ tbody: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("tbody", { children }), "tbody"),
4892
+ tr: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("tr", { className: "border-b border-border/50", children }), "tr"),
4893
+ th: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("th", { className: "px-2 py-1 text-left font-medium break-words", children }), "th"),
4894
+ td: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("td", { className: "px-2 py-1 break-words", children }), "td"),
4895
+ // Horizontal rule
4896
+ hr: /* @__PURE__ */ __name(() => /* @__PURE__ */ jsx("hr", { className: "my-3 border-0 h-px bg-border" }), "hr"),
4897
+ // Strong and emphasis
4898
+ strong: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold", children }), "strong"),
4899
+ em: /* @__PURE__ */ __name(({ children }) => /* @__PURE__ */ jsx("em", { className: "italic", children }), "em")
4900
+ };
4901
+ }, "createMarkdownComponents");
4902
+ var MarkdownMessage = /* @__PURE__ */ __name(({
4903
+ content,
4904
+ className = "",
4905
+ isUser = false,
4906
+ isCompact = false
4907
+ }) => {
4908
+ const components = React17.useMemo(() => createMarkdownComponents(isUser, isCompact), [isUser, isCompact]);
4909
+ const textSizeClass = isCompact ? "text-xs" : "text-sm";
4910
+ const proseClass = isCompact ? "prose-xs" : "prose-sm";
4911
+ return /* @__PURE__ */ jsx(
4912
+ "div",
4913
+ {
4914
+ className: `
4915
+ prose ${proseClass} max-w-none break-words overflow-hidden ${textSizeClass}
4916
+ ${isUser ? "prose-invert" : "dark:prose-invert"}
4917
+ ${className}
4918
+ `,
4919
+ style: {
4920
+ // Inherit colors from parent - fixes issues with external CSS variables
4921
+ "--tw-prose-body": "inherit",
4922
+ "--tw-prose-headings": "inherit",
4923
+ "--tw-prose-bold": "inherit",
4924
+ "--tw-prose-links": "inherit",
4925
+ color: "inherit"
4926
+ },
4927
+ children: /* @__PURE__ */ jsx(
4928
+ ReactMarkdown,
4929
+ {
4930
+ remarkPlugins: [remarkGfm],
4931
+ components,
4932
+ children: content
4933
+ }
4934
+ )
4935
+ }
4936
+ );
4937
+ }, "MarkdownMessage");
4938
+
4939
+ export { ArrayFieldItemTemplate, ArrayFieldTemplate, AudioReactiveCover, BaseInputTemplate, COLOR_SCHEMES2 as COLOR_SCHEMES, COLOR_SCHEME_INFO, CheckboxWidget, ColorWidget, EFFECT_ANIMATIONS, ErrorListTemplate, FieldTemplate, GlowEffect, HybridAudioPlayer, HybridAudioProvider, HybridSimplePlayer, HybridWaveform, INTENSITY_CONFIG, INTENSITY_INFO, ImageViewer, JsonSchemaForm, LottiePlayer, MarkdownMessage, Mermaid_default as Mermaid, MeshEffect, NativeProvider, NumberWidget, ObjectFieldTemplate, OpenapiViewer_default as OpenapiViewer, OrbsEffect, SelectWidget, SliderWidget, SpotlightEffect, StreamProvider, SwitchWidget, TextWidget, VARIANT_INFO, VideoControls, VideoErrorFallback, VideoPlayer, VideoPlayerProvider, VidstackProvider, VisualizationProvider, calculateGlowLayers, calculateMeshGradients, calculateOrbs, calculateSpotlight, createVideoErrorFallback, formatTime, generateContentKey, getColors, getEffectConfig, getRequiredFields, hasRequiredFields, isSimpleStreamSource, mergeDefaults, normalizeFormData, prepareEffectColors, resolveFileSource, resolvePlayerMode, resolveStreamSource, safeJsonParse, safeJsonStringify, useAudioCache, useAudioVisualization, useBlobUrlCleanup, useHybridAudio, useHybridAudioAnalysis, useHybridAudioContext, useHybridAudioControls, useHybridAudioLevels, useHybridAudioState, useHybridWebAudio, useImageCache, useMediaCacheStore, useVideoCache, useVideoPlayerContext, useVideoPlayerSettings, useVisualization, validateRequiredFields, validateSchema };
4940
+ //# sourceMappingURL=index.mjs.map
4941
+ //# sourceMappingURL=index.mjs.map