@fanvue/ui 3.15.0 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/cjs/components/AudioPlayer/AudioPlayer.cjs +16 -89
  2. package/dist/cjs/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
  3. package/dist/cjs/components/AudioRecordButton/AudioRecordButton.cjs +54 -0
  4. package/dist/cjs/components/AudioRecordButton/AudioRecordButton.cjs.map +1 -0
  5. package/dist/cjs/components/AudioUpload/AudioUpload.cjs +5 -5
  6. package/dist/cjs/components/AudioUpload/AudioUpload.cjs.map +1 -1
  7. package/dist/cjs/components/ChatInput/ChatInput.cjs +6 -11
  8. package/dist/cjs/components/ChatInput/ChatInput.cjs.map +1 -1
  9. package/dist/cjs/components/ChatMessage/ChatMessage.cjs +228 -0
  10. package/dist/cjs/components/ChatMessage/ChatMessage.cjs.map +1 -0
  11. package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs +160 -71
  12. package/dist/cjs/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  13. package/dist/cjs/components/Icons/DenseGridViewIcon.cjs +52 -0
  14. package/dist/cjs/components/Icons/DenseGridViewIcon.cjs.map +1 -0
  15. package/dist/cjs/components/MediaStatusIndicator/MediaStatusIndicator.cjs +66 -0
  16. package/dist/cjs/components/MediaStatusIndicator/MediaStatusIndicator.cjs.map +1 -0
  17. package/dist/cjs/components/Select/Select.cjs +61 -17
  18. package/dist/cjs/components/Select/Select.cjs.map +1 -1
  19. package/dist/cjs/components/SubscribeButton/SubscribeButton.cjs +53 -0
  20. package/dist/cjs/components/SubscribeButton/SubscribeButton.cjs.map +1 -0
  21. package/dist/cjs/components/VoiceNote/VoiceNote.cjs +305 -0
  22. package/dist/cjs/components/VoiceNote/VoiceNote.cjs.map +1 -0
  23. package/dist/cjs/index.cjs +12 -0
  24. package/dist/cjs/index.cjs.map +1 -1
  25. package/dist/cjs/utils/audioWaveform.cjs +76 -0
  26. package/dist/cjs/utils/audioWaveform.cjs.map +1 -0
  27. package/dist/cjs/utils/useAudioPlayback.cjs +123 -0
  28. package/dist/cjs/utils/useAudioPlayback.cjs.map +1 -0
  29. package/dist/cjs/utils/useFittedBarCount.cjs +42 -0
  30. package/dist/cjs/utils/useFittedBarCount.cjs.map +1 -0
  31. package/dist/cjs/utils/useWaveformPeaks.cjs +48 -0
  32. package/dist/cjs/utils/useWaveformPeaks.cjs.map +1 -0
  33. package/dist/cjs/utils/useWaveformSeek.cjs +100 -0
  34. package/dist/cjs/utils/useWaveformSeek.cjs.map +1 -0
  35. package/dist/components/AudioPlayer/AudioPlayer.mjs +7 -80
  36. package/dist/components/AudioPlayer/AudioPlayer.mjs.map +1 -1
  37. package/dist/components/AudioRecordButton/AudioRecordButton.mjs +37 -0
  38. package/dist/components/AudioRecordButton/AudioRecordButton.mjs.map +1 -0
  39. package/dist/components/AudioUpload/AudioUpload.mjs +5 -5
  40. package/dist/components/AudioUpload/AudioUpload.mjs.map +1 -1
  41. package/dist/components/ChatInput/ChatInput.mjs +6 -11
  42. package/dist/components/ChatInput/ChatInput.mjs.map +1 -1
  43. package/dist/components/ChatMessage/ChatMessage.mjs +211 -0
  44. package/dist/components/ChatMessage/ChatMessage.mjs.map +1 -0
  45. package/dist/components/DropdownMenu/DropdownMenu.mjs +161 -72
  46. package/dist/components/DropdownMenu/DropdownMenu.mjs.map +1 -1
  47. package/dist/components/Icons/DenseGridViewIcon.mjs +35 -0
  48. package/dist/components/Icons/DenseGridViewIcon.mjs.map +1 -0
  49. package/dist/components/MediaStatusIndicator/MediaStatusIndicator.mjs +49 -0
  50. package/dist/components/MediaStatusIndicator/MediaStatusIndicator.mjs.map +1 -0
  51. package/dist/components/Select/Select.mjs +61 -17
  52. package/dist/components/Select/Select.mjs.map +1 -1
  53. package/dist/components/SubscribeButton/SubscribeButton.mjs +36 -0
  54. package/dist/components/SubscribeButton/SubscribeButton.mjs.map +1 -0
  55. package/dist/components/VoiceNote/VoiceNote.mjs +288 -0
  56. package/dist/components/VoiceNote/VoiceNote.mjs.map +1 -0
  57. package/dist/index.d.ts +321 -4
  58. package/dist/index.mjs +12 -0
  59. package/dist/index.mjs.map +1 -1
  60. package/dist/styles/base.css +11 -2
  61. package/dist/styles/theme.css +1 -1
  62. package/dist/utils/audioWaveform.mjs +76 -0
  63. package/dist/utils/audioWaveform.mjs.map +1 -0
  64. package/dist/utils/useAudioPlayback.mjs +106 -0
  65. package/dist/utils/useAudioPlayback.mjs.map +1 -0
  66. package/dist/utils/useFittedBarCount.mjs +25 -0
  67. package/dist/utils/useFittedBarCount.mjs.map +1 -0
  68. package/dist/utils/useWaveformPeaks.mjs +31 -0
  69. package/dist/utils/useWaveformPeaks.mjs.map +1 -0
  70. package/dist/utils/useWaveformSeek.mjs +83 -0
  71. package/dist/utils/useWaveformSeek.mjs.map +1 -0
  72. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceNote.cjs","sources":["../../../../src/components/VoiceNote/VoiceNote.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { formatTime, resamplePeaks } from \"@/utils/audioWaveform\";\nimport { cn } from \"@/utils/cn\";\nimport { useAudioPlayback } from \"@/utils/useAudioPlayback\";\nimport { useFittedBarCount } from \"@/utils/useFittedBarCount\";\nimport { useWaveformPeaks } from \"@/utils/useWaveformPeaks\";\nimport { useWaveformSeek } from \"@/utils/useWaveformSeek\";\nimport { IconButton, type IconButtonSize } from \"../IconButton/IconButton\";\nimport { CloseIcon } from \"../Icons/CloseIcon\";\nimport { PauseIcon } from \"../Icons/PauseIcon\";\nimport { PlayIcon } from \"../Icons/PlayIcon\";\n\n/** Visual style of the waveform. */\nexport type VoiceNoteVariant = \"default\" | \"flat\";\n\n/** Size preset for the voice note. */\nexport type VoiceNoteSize = \"default\" | \"small\";\n\n/**\n * Default waveform amplitudes (0–1), taken from the Figma reference so the\n * resting state renders 1:1 when no `waveform` data (or `src`) is supplied.\n */\nconst DEFAULT_WAVEFORM = [\n 0.15, 0.46, 0.85, 0.23, 1, 0.85, 0.62, 0.62, 0.62, 0.85, 0.85, 0.62, 0.62, 1, 1, 0.62, 0.62, 0.62,\n 0.23, 0.23, 0.15, 0.46, 0.85, 0.23, 1, 0.46, 0.85, 0.62, 0.23, 0.23, 0.62, 0.62, 0.85, 0.85, 0.62,\n 0.62, 0.85, 1, 0.85, 1, 0.62, 0.62, 0.62, 0.23, 0.23, 0.62, 0.23, 0.23, 0.23, 0.62, 0.85, 0.62,\n 0.85, 1, 0.85, 0.85, 1, 0.85, 0.23, 0.23, 0.23, 0.46, 0.62, 0.23, 0.23,\n];\n\nconst MIN_BAR_HEIGHT = 4;\nconst MAX_BAR_HEIGHT: Record<VoiceNoteSize, number> = {\n default: 26,\n small: 16,\n};\n\nconst CONTROL_SIZE: Record<VoiceNoteSize, IconButtonSize> = {\n default: \"48\",\n small: \"32\",\n};\n\n/** Rendered width of a single waveform bar, in pixels (`w-1`). */\nconst BAR_WIDTH_PX = 4;\n/** Gap between waveform bars, in pixels (`gap-1`). */\nconst BAR_GAP_PX = 4;\n/** Bar count before the track is measured (SSR, jsdom, or zero-width layout). */\nconst DEFAULT_BAR_COUNT = 40;\n\nfunction activeBarClass(negative: boolean): string {\n return negative\n ? \"bg-messages-waveform-listening-negative-active\"\n : \"bg-messages-waveform-listening-active\";\n}\n\nfunction inactiveBarClass(negative: boolean): string {\n return negative\n ? \"bg-messages-waveform-listening-negative-inactive\"\n : \"bg-messages-waveform-listening-inactive\";\n}\n\nfunction restingBarClass(negative: boolean): string {\n return negative\n ? \"bg-messages-waveform-listening-negative-default\"\n : \"bg-messages-waveform-default\";\n}\n\ninterface WaveformBarsProps {\n bars: number[];\n variant: VoiceNoteVariant;\n size: VoiceNoteSize;\n negative: boolean;\n progress: number | undefined;\n}\n\nfunction WaveformBars({ bars, variant, size, negative, progress }: WaveformBarsProps) {\n const hasProgress = progress !== undefined;\n const activeCount = hasProgress ? Math.round(progress * bars.length) : 0;\n const maxBarHeight = MAX_BAR_HEIGHT[size];\n\n return (\n <>\n {bars.map((amplitude, index) => {\n const height =\n variant === \"flat\"\n ? MIN_BAR_HEIGHT\n : Math.max(MIN_BAR_HEIGHT, Math.round(amplitude * maxBarHeight));\n const colorClass = !hasProgress\n ? restingBarClass(negative)\n : index < activeCount\n ? activeBarClass(negative)\n : inactiveBarClass(negative);\n\n return (\n <span\n // biome-ignore lint/suspicious/noArrayIndexKey: bars are a fixed positional sequence\n key={index}\n className={cn(\"w-1 shrink-0 rounded-[2px]\", colorClass)}\n style={{ height }}\n />\n );\n })}\n </>\n );\n}\n\ninterface WaveformMetaProps {\n time: string | undefined;\n fileName: string | undefined;\n textColor: string;\n}\n\nfunction WaveformMeta({ time, fileName, textColor }: WaveformMetaProps) {\n return (\n <>\n {time && (\n <span className={cn(\"typography-body-small-14px-regular shrink-0\", textColor)}>{time}</span>\n )}\n {fileName && (\n <span\n className={cn(\n \"typography-body-small-14px-regular min-w-0 max-w-[50%] truncate\",\n textColor,\n )}\n >\n {fileName}\n </span>\n )}\n </>\n );\n}\n\nexport interface VoiceNoteProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onPlay\" | \"onPause\"> {\n /**\n * URL of the audio to play. When set, the component manages a real `<audio>`\n * element: it decodes the waveform, plays/pauses, tracks live progress and is\n * seekable — `waveform`/`progress` are derived automatically. Leave unset for\n * a presentational, fully-controlled voice note.\n */\n src?: string;\n /** Fallback total duration (seconds), used until the media's metadata loads. */\n duration?: number;\n /** Amplitude values (0–1), one per bar. Ignored when `src` is set. Falls back to a built-in pattern. */\n waveform?: number[];\n /** Visual style; `flat` renders a simplified dotted preview. @default \"default\" */\n variant?: VoiceNoteVariant;\n /** Size preset. @default \"default\" */\n size?: VoiceNoteSize;\n /** Dark-surface treatment for use on message bubbles. @default false */\n negative?: boolean;\n /**\n * Playback progress (0–1) for the presentational mode. When set, the waveform\n * splits into played/unplayed bars (the \"Listening\" state). Ignored when `src`\n * is set (progress comes from the media element).\n */\n progress?: number;\n /** Whether audio is playing (controlled) — toggles the play/pause icon. */\n playing?: boolean;\n /** Initial playing state (uncontrolled). @default false */\n defaultPlaying?: boolean;\n /** Called with the next playing state when the play/pause control is pressed. */\n onPlayPause?: (playing: boolean) => void;\n /** Called when audio playback reaches the end (only in `src` mode). */\n onEnded?: () => void;\n /** Timestamp or duration label, e.g. \"0:05\". Ignored when `src` is set (derived from the media). */\n time?: string;\n /** File name shown when the audio is an uploaded file rather than a voice note. */\n fileName?: string;\n /** Show the play/pause control. @default true */\n showControls?: boolean;\n /** Show the timestamp label. @default true */\n showTimestamp?: boolean;\n /** Show a remove button (calls {@link VoiceNoteProps.onRemove}). @default false */\n showRemove?: boolean;\n /** Called when the remove button is pressed. */\n onRemove?: () => void;\n /** Accessible name for the play/pause control. Defaults to \"Play\"/\"Pause\". */\n playButtonLabel?: string;\n /** Accessible name for the remove button. @default \"Remove\" */\n removeButtonLabel?: string;\n /** Accessible name for the whole player. @default \"Voice note\" */\n \"aria-label\"?: string;\n}\n\n/**\n * A voice-note audio player: a play/pause control, an amplitude waveform, and a\n * timestamp — for voice messages and audio attachments in a conversation.\n *\n * Two modes: pass `src` for a self-contained player that decodes the waveform,\n * plays real audio, tracks live progress and is seekable; or omit `src` and\n * drive it with `playing`/`progress` + `onPlayPause` for a presentational,\n * fully-controlled voice note. `flat` renders a compact dotted preview and\n * `negative` adapts it to dark message bubbles.\n *\n * @example\n * ```tsx\n * <VoiceNote src=\"https://example.com/note.mp3\" duration={5} />\n * <VoiceNote time=\"0:05\" progress={0.4} playing onPlayPause={toggle} />\n * ```\n */\nexport const VoiceNote = React.forwardRef<HTMLDivElement, VoiceNoteProps>(\n (\n {\n className,\n src,\n duration,\n waveform = DEFAULT_WAVEFORM,\n variant = \"default\",\n size = \"default\",\n negative = false,\n progress,\n playing,\n defaultPlaying = false,\n onPlayPause,\n onEnded,\n time,\n fileName,\n showControls = true,\n showTimestamp = true,\n showRemove = false,\n onRemove,\n playButtonLabel,\n removeButtonLabel = \"Remove\",\n \"aria-label\": ariaLabel = \"Voice note\",\n ...props\n },\n ref,\n ) => {\n const isPlayer = src !== undefined;\n\n const playback = useAudioPlayback({\n src,\n duration,\n playing,\n defaultPlaying,\n onEnded,\n onPlay: React.useCallback(() => onPlayPause?.(true), [onPlayPause]),\n onPause: React.useCallback(() => onPlayPause?.(false), [onPlayPause]),\n });\n const decodedPeaks = useWaveformPeaks(src);\n\n const [internalPlaying, setInternalPlaying] = React.useState(defaultPlaying);\n const isControlled = playing !== undefined;\n\n const trackRef = React.useRef<HTMLDivElement>(null);\n const fittedBarCount = useFittedBarCount(trackRef, {\n barWidthPx: BAR_WIDTH_PX,\n gapPx: BAR_GAP_PX,\n fallback: DEFAULT_BAR_COUNT,\n });\n const seekProps = useWaveformSeek({\n trackRef,\n displayDuration: playback.displayDuration,\n currentTime: playback.currentTime,\n seekTo: playback.seekTo,\n setSeeking: playback.setSeeking,\n });\n\n const isPlaying = isPlayer ? playback.isPlaying : (playing ?? internalPlaying);\n\n const handlePlayPause = () => {\n if (isPlayer) {\n playback.toggle();\n return;\n }\n const next = !isPlaying;\n if (!isControlled) setInternalPlaying(next);\n onPlayPause?.(next);\n };\n\n const bars = resamplePeaks(isPlayer ? decodedPeaks : waveform, fittedBarCount);\n const progressValue = isPlayer ? playback.progress : progress;\n const textColor = negative ? \"text-content-primary-inverted\" : \"text-content-primary\";\n\n const playerTime = playback.hasStarted ? playback.currentTime : playback.displayDuration;\n const displayTime = isPlayer ? formatTime(playerTime) : time;\n const controlLabel = playButtonLabel ?? (isPlaying ? \"Pause\" : \"Play\");\n const trackAria = isPlayer ? seekProps : ({ \"aria-hidden\": true } as const);\n\n return (\n // biome-ignore lint/a11y/useSemanticElements: <fieldset> would break the public HTMLDivElement ref/props API\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel}\n className={cn(\"flex items-center gap-3\", size === \"small\" ? \"h-8\" : \"h-12\", className)}\n {...props}\n >\n {showControls && (\n <IconButton\n variant=\"secondary\"\n size={CONTROL_SIZE[size]}\n negative={negative}\n icon={isPlaying ? <PauseIcon /> : <PlayIcon />}\n aria-label={controlLabel}\n onClick={handlePlayPause}\n />\n )}\n\n <div\n ref={trackRef}\n data-testid=\"voice-note-waveform\"\n className={cn(\n \"flex h-full min-w-0 flex-1 items-center gap-1 overflow-hidden\",\n isPlayer &&\n \"cursor-pointer touch-none select-none focus-visible:shadow-focus-ring focus-visible:outline-none\",\n )}\n {...trackAria}\n >\n <WaveformBars\n bars={bars}\n variant={variant}\n size={size}\n negative={negative}\n progress={progressValue}\n />\n </div>\n\n <WaveformMeta\n time={showTimestamp ? displayTime : undefined}\n fileName={fileName}\n textColor={textColor}\n />\n\n {showRemove && (\n <IconButton\n variant=\"tertiary\"\n size={CONTROL_SIZE[size]}\n negative={negative}\n icon={<CloseIcon />}\n aria-label={removeButtonLabel}\n onClick={onRemove}\n />\n )}\n\n {isPlayer && (\n // biome-ignore lint/a11y/useMediaCaption: a UI voice note / short recording, not narrated content needing captions\n <audio\n ref={playback.audioRef}\n src={src}\n preload=\"metadata\"\n className=\"hidden\"\n onLoadedMetadata={playback.audioHandlers.onLoadedMetadata}\n onTimeUpdate={playback.audioHandlers.onTimeUpdate}\n onEnded={playback.audioHandlers.onEnded}\n />\n )}\n </div>\n );\n },\n);\n\nVoiceNote.displayName = \"VoiceNote\";\n"],"names":["jsx","Fragment","cn","jsxs","React","useAudioPlayback","useWaveformPeaks","useFittedBarCount","useWaveformSeek","resamplePeaks","formatTime","IconButton","PauseIcon","PlayIcon","CloseIcon"],"mappings":"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"}
@@ -8,6 +8,7 @@ const AccordionItem = require("./components/Accordion/AccordionItem.cjs");
8
8
  const AccordionTrigger = require("./components/Accordion/AccordionTrigger.cjs");
9
9
  const Alert = require("./components/Alert/Alert.cjs");
10
10
  const AudioPlayer = require("./components/AudioPlayer/AudioPlayer.cjs");
11
+ const AudioRecordButton = require("./components/AudioRecordButton/AudioRecordButton.cjs");
11
12
  const AudioUpload = require("./components/AudioUpload/AudioUpload.cjs");
12
13
  const useAudioRecorder = require("./components/AudioUpload/useAudioRecorder.cjs");
13
14
  const Autocomplete = require("./components/Autocomplete/Autocomplete.cjs");
@@ -20,6 +21,7 @@ const Breadcrumb = require("./components/Breadcrumb/Breadcrumb.cjs");
20
21
  const Button = require("./components/Button/Button.cjs");
21
22
  const Card = require("./components/Card/Card.cjs");
22
23
  const ChatInput = require("./components/ChatInput/ChatInput.cjs");
24
+ const ChatMessage = require("./components/ChatMessage/ChatMessage.cjs");
23
25
  const Checkbox = require("./components/Checkbox/Checkbox.cjs");
24
26
  const Chip = require("./components/Chip/Chip.cjs");
25
27
  const Count = require("./components/Count/Count.cjs");
@@ -79,6 +81,7 @@ const CopyIcon = require("./components/Icons/CopyIcon.cjs");
79
81
  const CrossCircleIcon = require("./components/Icons/CrossCircleIcon.cjs");
80
82
  const CrossIcon = require("./components/Icons/CrossIcon.cjs");
81
83
  const CrownIcon = require("./components/Icons/CrownIcon.cjs");
84
+ const DenseGridViewIcon = require("./components/Icons/DenseGridViewIcon.cjs");
82
85
  const DiamondIcon = require("./components/Icons/DiamondIcon.cjs");
83
86
  const DiscordIcon = require("./components/Icons/DiscordIcon.cjs");
84
87
  const DiscountIcon = require("./components/Icons/DiscountIcon.cjs");
@@ -206,6 +209,7 @@ const InlineEdit = require("./components/InlineEdit/InlineEdit.cjs");
206
209
  const Link = require("./components/Link/Link.cjs");
207
210
  const Loader = require("./components/Loader/Loader.cjs");
208
211
  const Logo = require("./components/Logo/Logo.cjs");
212
+ const MediaStatusIndicator = require("./components/MediaStatusIndicator/MediaStatusIndicator.cjs");
209
213
  const MobileStepper = require("./components/MobileStepper/MobileStepper.cjs");
210
214
  const OnlineBlinkingIcon = require("./components/OnlineBlinkingIcon/OnlineBlinkingIcon.cjs");
211
215
  const PageSelector = require("./components/PageSelector/PageSelector.cjs");
@@ -229,6 +233,7 @@ const Slider = require("./components/Slider/Slider.cjs");
229
233
  const Snackbar = require("./components/Snackbar/Snackbar.cjs");
230
234
  const Stepper = require("./components/Stepper/Stepper.cjs");
231
235
  const StepperStep = require("./components/Stepper/StepperStep.cjs");
236
+ const SubscribeButton = require("./components/SubscribeButton/SubscribeButton.cjs");
232
237
  const Switch = require("./components/Switch/Switch.cjs");
233
238
  const SwitchField = require("./components/SwitchField/SwitchField.cjs");
234
239
  const SwitchToggle = require("./components/SwitchToggle/SwitchToggle.cjs");
@@ -245,6 +250,7 @@ const Tooltip = require("./components/Tooltip/Tooltip.cjs");
245
250
  const UserDisplayName = require("./components/UserDisplayName/UserDisplayName.cjs");
246
251
  const UserHandle = require("./components/UserHandle/UserHandle.cjs");
247
252
  const UserItem = require("./components/UserItem/UserItem.cjs");
253
+ const VoiceNote = require("./components/VoiceNote/VoiceNote.cjs");
248
254
  const cn = require("./utils/cn.cjs");
249
255
  const getInitials = require("./utils/getInitials.cjs");
250
256
  const useSuppressClickAfterDrag = require("./utils/useSuppressClickAfterDrag.cjs");
@@ -262,6 +268,7 @@ exports.AccordionItem = AccordionItem.AccordionItem;
262
268
  exports.AccordionTrigger = AccordionTrigger.AccordionTrigger;
263
269
  exports.Alert = Alert.Alert;
264
270
  exports.AudioPlayer = AudioPlayer.AudioPlayer;
271
+ exports.AudioRecordButton = AudioRecordButton.AudioRecordButton;
265
272
  exports.AudioUpload = AudioUpload.AudioUpload;
266
273
  exports.useAudioRecorder = useAudioRecorder.useAudioRecorder;
267
274
  exports.Autocomplete = Autocomplete.Autocomplete;
@@ -287,6 +294,7 @@ exports.CardFooter = Card.CardFooter;
287
294
  exports.CardHeader = Card.CardHeader;
288
295
  exports.CardTitle = Card.CardTitle;
289
296
  exports.ChatInput = ChatInput.ChatInput;
297
+ exports.ChatMessage = ChatMessage.ChatMessage;
290
298
  exports.Checkbox = Checkbox.Checkbox;
291
299
  exports.Chip = Chip.Chip;
292
300
  exports.Count = Count.Count;
@@ -372,6 +380,7 @@ exports.CopyIcon = CopyIcon.CopyIcon;
372
380
  exports.CrossCircleIcon = CrossCircleIcon.CrossCircleIcon;
373
381
  exports.CrossIcon = CrossIcon.CrossIcon;
374
382
  exports.CrownIcon = CrownIcon.CrownIcon;
383
+ exports.DenseGridViewIcon = DenseGridViewIcon.DenseGridViewIcon;
375
384
  exports.DiamondIcon = DiamondIcon.DiamondIcon;
376
385
  exports.DiscordIcon = DiscordIcon.DiscordIcon;
377
386
  exports.DiscountIcon = DiscountIcon.DiscountIcon;
@@ -501,6 +510,7 @@ exports.InlineEdit = InlineEdit.InlineEdit;
501
510
  exports.Link = Link.Link;
502
511
  exports.Loader = Loader.Loader;
503
512
  exports.Logo = Logo.Logo;
513
+ exports.MediaStatusIndicator = MediaStatusIndicator.MediaStatusIndicator;
504
514
  exports.MobileStepper = MobileStepper.MobileStepper;
505
515
  exports.OnlineBlinkingIcon = OnlineBlinkingIcon.OnlineBlinkingIcon;
506
516
  exports.PageSelector = PageSelector.PageSelector;
@@ -529,6 +539,7 @@ exports.Slider = Slider.Slider;
529
539
  exports.Snackbar = Snackbar.Snackbar;
530
540
  exports.Stepper = Stepper.Stepper;
531
541
  exports.StepperStep = StepperStep.StepperStep;
542
+ exports.SubscribeButton = SubscribeButton.SubscribeButton;
532
543
  exports.Switch = Switch.Switch;
533
544
  exports.SwitchField = SwitchField.SwitchField;
534
545
  exports.SwitchToggle = SwitchToggle.SwitchToggle;
@@ -569,6 +580,7 @@ exports.TooltipTrigger = Tooltip.TooltipTrigger;
569
580
  exports.UserDisplayName = UserDisplayName.UserDisplayName;
570
581
  exports.UserHandle = UserHandle.UserHandle;
571
582
  exports.UserItem = UserItem.UserItem;
583
+ exports.VoiceNote = VoiceNote.VoiceNote;
572
584
  exports.cn = cn.cn;
573
585
  exports.getInitials = getInitials.getInitials;
574
586
  exports.useSuppressClickAfterDrag = useSuppressClickAfterDrag.useSuppressClickAfterDrag;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,76 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const RAW_PEAK_COUNT = 128;
5
+ function formatTime(seconds) {
6
+ if (seconds === void 0 || !Number.isFinite(seconds)) return "--:--";
7
+ const totalSeconds = Math.max(0, Math.floor(seconds));
8
+ const minutes = Math.floor(totalSeconds / 60);
9
+ const remainingSeconds = totalSeconds % 60;
10
+ return `${minutes}:${String(remainingSeconds).padStart(2, "0")}`;
11
+ }
12
+ function hashString(value) {
13
+ let hash = 2166136261;
14
+ for (let i = 0; i < value.length; i++) {
15
+ hash ^= value.charCodeAt(i);
16
+ hash = Math.imul(hash, 16777619);
17
+ }
18
+ return hash >>> 0;
19
+ }
20
+ function createSeededRandom(seed) {
21
+ let state = seed;
22
+ return () => {
23
+ state = state + 1831565813 | 0;
24
+ let t = Math.imul(state ^ state >>> 15, 1 | state);
25
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
26
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
27
+ };
28
+ }
29
+ function generateFallbackPeaks(src, count) {
30
+ const random = createSeededRandom(hashString(src));
31
+ return Array.from({ length: count }, () => 0.2 + random() * 0.8);
32
+ }
33
+ function computePeaksFromChannelData(channelData, count) {
34
+ const blockSize = Math.max(1, Math.floor(channelData.length / count));
35
+ const peaks = [];
36
+ for (let i = 0; i < count; i++) {
37
+ const start = i * blockSize;
38
+ let max = 0;
39
+ for (let j = 0; j < blockSize && start + j < channelData.length; j++) {
40
+ max = Math.max(max, Math.abs(channelData[start + j] ?? 0));
41
+ }
42
+ peaks.push(max);
43
+ }
44
+ return peaks;
45
+ }
46
+ function resamplePeaks(peaks, barCount) {
47
+ if (peaks.length === 0 || barCount <= 0) return [];
48
+ const step = peaks.length / barCount;
49
+ return Array.from(
50
+ { length: barCount },
51
+ (_, i) => peaks[Math.min(peaks.length - 1, Math.floor(i * step))] ?? 0
52
+ );
53
+ }
54
+ async function decodeAudioPeaks(src, signal) {
55
+ const AudioContextCtor = window.AudioContext ?? window.webkitAudioContext;
56
+ if (!AudioContextCtor) throw new Error("WebAudio unsupported");
57
+ const response = await fetch(src, { signal });
58
+ const arrayBuffer = await response.arrayBuffer();
59
+ const audioContext = new AudioContextCtor();
60
+ try {
61
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
62
+ return computePeaksFromChannelData(audioBuffer.getChannelData(0), RAW_PEAK_COUNT);
63
+ } finally {
64
+ if (audioContext.state !== "closed") audioContext.close().catch(() => {
65
+ });
66
+ }
67
+ }
68
+ exports.RAW_PEAK_COUNT = RAW_PEAK_COUNT;
69
+ exports.computePeaksFromChannelData = computePeaksFromChannelData;
70
+ exports.createSeededRandom = createSeededRandom;
71
+ exports.decodeAudioPeaks = decodeAudioPeaks;
72
+ exports.formatTime = formatTime;
73
+ exports.generateFallbackPeaks = generateFallbackPeaks;
74
+ exports.hashString = hashString;
75
+ exports.resamplePeaks = resamplePeaks;
76
+ //# sourceMappingURL=audioWaveform.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audioWaveform.cjs","sources":["../../../src/utils/audioWaveform.ts"],"sourcesContent":["/** Number of amplitude samples decoded/kept internally, resampled to the rendered bar count. */\nexport const RAW_PEAK_COUNT = 128;\n\n/** Formats a duration in seconds as `m:ss`, or `--:--` when unknown. */\nexport function formatTime(seconds: number | undefined): string {\n if (seconds === undefined || !Number.isFinite(seconds)) return \"--:--\";\n const totalSeconds = Math.max(0, Math.floor(seconds));\n const minutes = Math.floor(totalSeconds / 60);\n const remainingSeconds = totalSeconds % 60;\n return `${minutes}:${String(remainingSeconds).padStart(2, \"0\")}`;\n}\n\n/** Deterministic 32-bit string hash (FNV-1a), used to seed the fallback waveform. */\nexport function hashString(value: string): number {\n let hash = 2166136261;\n for (let i = 0; i < value.length; i++) {\n hash ^= value.charCodeAt(i);\n hash = Math.imul(hash, 16777619);\n }\n return hash >>> 0;\n}\n\n/** Seeded PRNG (mulberry32) — deterministic across runs, unlike `Math.random`. */\nexport function createSeededRandom(seed: number): () => number {\n let state = seed;\n return () => {\n state = (state + 0x6d2b79f5) | 0;\n let t = Math.imul(state ^ (state >>> 15), 1 | state);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\n/** Deterministic placeholder amplitudes used when audio can't be decoded (network/CORS/format failure). */\nexport function generateFallbackPeaks(src: string, count: number): number[] {\n const random = createSeededRandom(hashString(src));\n return Array.from({ length: count }, () => 0.2 + random() * 0.8);\n}\n\n/** Downsamples decoded PCM data to `count` peak (max-abs) amplitudes. */\nexport function computePeaksFromChannelData(channelData: Float32Array, count: number): number[] {\n const blockSize = Math.max(1, Math.floor(channelData.length / count));\n const peaks: number[] = [];\n for (let i = 0; i < count; i++) {\n const start = i * blockSize;\n let max = 0;\n for (let j = 0; j < blockSize && start + j < channelData.length; j++) {\n max = Math.max(max, Math.abs(channelData[start + j] ?? 0));\n }\n peaks.push(max);\n }\n return peaks;\n}\n\n/** Resamples a peaks array to `barCount` values (nearest-neighbour). */\nexport function resamplePeaks(peaks: number[], barCount: number): number[] {\n if (peaks.length === 0 || barCount <= 0) return [];\n const step = peaks.length / barCount;\n return Array.from(\n { length: barCount },\n (_, i) => peaks[Math.min(peaks.length - 1, Math.floor(i * step))] ?? 0,\n );\n}\n\n/** Decodes `src` via WebAudio and returns downsampled peak amplitudes. Throws on any failure. */\nexport async function decodeAudioPeaks(src: string, signal: AbortSignal): Promise<number[]> {\n const AudioContextCtor =\n window.AudioContext ??\n (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!AudioContextCtor) throw new Error(\"WebAudio unsupported\");\n\n const response = await fetch(src, { signal });\n const arrayBuffer = await response.arrayBuffer();\n const audioContext = new AudioContextCtor();\n try {\n const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);\n return computePeaksFromChannelData(audioBuffer.getChannelData(0), RAW_PEAK_COUNT);\n } finally {\n if (audioContext.state !== \"closed\") audioContext.close().catch(() => {});\n }\n}\n"],"names":[],"mappings":";;;AACO,MAAM,iBAAiB;AAGvB,SAAS,WAAW,SAAqC;AAC9D,MAAI,YAAY,UAAa,CAAC,OAAO,SAAS,OAAO,EAAG,QAAO;AAC/D,QAAM,eAAe,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC;AACpD,QAAM,UAAU,KAAK,MAAM,eAAe,EAAE;AAC5C,QAAM,mBAAmB,eAAe;AACxC,SAAO,GAAG,OAAO,IAAI,OAAO,gBAAgB,EAAE,SAAS,GAAG,GAAG,CAAC;AAChE;AAGO,SAAS,WAAW,OAAuB;AAChD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAQ,MAAM,WAAW,CAAC;AAC1B,WAAO,KAAK,KAAK,MAAM,QAAQ;AAAA,EACjC;AACA,SAAO,SAAS;AAClB;AAGO,SAAS,mBAAmB,MAA4B;AAC7D,MAAI,QAAQ;AACZ,SAAO,MAAM;AACX,YAAS,QAAQ,aAAc;AAC/B,QAAI,IAAI,KAAK,KAAK,QAAS,UAAU,IAAK,IAAI,KAAK;AACnD,QAAK,IAAI,KAAK,KAAK,IAAK,MAAM,GAAI,KAAK,CAAC,IAAK;AAC7C,aAAS,IAAK,MAAM,QAAS,KAAK;AAAA,EACpC;AACF;AAGO,SAAS,sBAAsB,KAAa,OAAyB;AAC1E,QAAM,SAAS,mBAAmB,WAAW,GAAG,CAAC;AACjD,SAAO,MAAM,KAAK,EAAE,QAAQ,MAAA,GAAS,MAAM,MAAM,OAAA,IAAW,GAAG;AACjE;AAGO,SAAS,4BAA4B,aAA2B,OAAyB;AAC9F,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,YAAY,SAAS,KAAK,CAAC;AACpE,QAAM,QAAkB,CAAA;AACxB,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAM,QAAQ,IAAI;AAClB,QAAI,MAAM;AACV,aAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,IAAI,YAAY,QAAQ,KAAK;AACpE,YAAM,KAAK,IAAI,KAAK,KAAK,IAAI,YAAY,QAAQ,CAAC,KAAK,CAAC,CAAC;AAAA,IAC3D;AACA,UAAM,KAAK,GAAG;AAAA,EAChB;AACA,SAAO;AACT;AAGO,SAAS,cAAc,OAAiB,UAA4B;AACzE,MAAI,MAAM,WAAW,KAAK,YAAY,UAAU,CAAA;AAChD,QAAM,OAAO,MAAM,SAAS;AAC5B,SAAO,MAAM;AAAA,IACX,EAAE,QAAQ,SAAA;AAAA,IACV,CAAC,GAAG,MAAM,MAAM,KAAK,IAAI,MAAM,SAAS,GAAG,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK;AAAA,EAAA;AAEzE;AAGA,eAAsB,iBAAiB,KAAa,QAAwC;AAC1F,QAAM,mBACJ,OAAO,gBACN,OAAmE;AACtE,MAAI,CAAC,iBAAkB,OAAM,IAAI,MAAM,sBAAsB;AAE7D,QAAM,WAAW,MAAM,MAAM,KAAK,EAAE,QAAQ;AAC5C,QAAM,cAAc,MAAM,SAAS,YAAA;AACnC,QAAM,eAAe,IAAI,iBAAA;AACzB,MAAI;AACF,UAAM,cAAc,MAAM,aAAa,gBAAgB,WAAW;AAClE,WAAO,4BAA4B,YAAY,eAAe,CAAC,GAAG,cAAc;AAAA,EAClF,UAAA;AACE,QAAI,aAAa,UAAU,uBAAuB,MAAA,EAAQ,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC1E;AACF;;;;;;;;;"}
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const React = require("react");
5
+ function _interopNamespaceDefault(e) {
6
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
7
+ if (e) {
8
+ for (const k in e) {
9
+ if (k !== "default") {
10
+ const d = Object.getOwnPropertyDescriptor(e, k);
11
+ Object.defineProperty(n, k, d.get ? d : {
12
+ enumerable: true,
13
+ get: () => e[k]
14
+ });
15
+ }
16
+ }
17
+ }
18
+ n.default = e;
19
+ return Object.freeze(n);
20
+ }
21
+ const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
22
+ function useAudioPlayback({
23
+ src,
24
+ duration,
25
+ playing: controlledPlaying,
26
+ defaultPlaying = false,
27
+ onPlay,
28
+ onPause,
29
+ onEnded
30
+ }) {
31
+ const audioRef = React__namespace.useRef(null);
32
+ const [internalPlaying, setInternalPlaying] = React__namespace.useState(defaultPlaying);
33
+ const isControlled = controlledPlaying !== void 0;
34
+ const isPlaying = isControlled ? controlledPlaying : internalPlaying;
35
+ const [currentTime, setCurrentTime] = React__namespace.useState(0);
36
+ const [mediaDuration, setMediaDuration] = React__namespace.useState(void 0);
37
+ const seekingRef = React__namespace.useRef(false);
38
+ const displayDuration = mediaDuration ?? duration;
39
+ const hasStarted = isPlaying || currentTime > 0;
40
+ const playingRef = React__namespace.useRef(isPlaying);
41
+ playingRef.current = isPlaying;
42
+ const isControlledRef = React__namespace.useRef(isControlled);
43
+ isControlledRef.current = isControlled;
44
+ const onPauseRef = React__namespace.useRef(onPause);
45
+ onPauseRef.current = onPause;
46
+ const isFirstSrcRenderRef = React__namespace.useRef(true);
47
+ React__namespace.useEffect(() => {
48
+ if (isFirstSrcRenderRef.current) {
49
+ isFirstSrcRenderRef.current = false;
50
+ return;
51
+ }
52
+ setCurrentTime(0);
53
+ setMediaDuration(void 0);
54
+ if (playingRef.current) {
55
+ if (!isControlledRef.current) setInternalPlaying(false);
56
+ onPauseRef.current?.();
57
+ }
58
+ }, [src]);
59
+ React__namespace.useEffect(() => {
60
+ const audio = audioRef.current;
61
+ if (!audio) return;
62
+ if (isPlaying) {
63
+ audio.play().catch(() => {
64
+ if (!isControlled) setInternalPlaying(false);
65
+ onPauseRef.current?.();
66
+ });
67
+ } else {
68
+ audio.pause();
69
+ }
70
+ }, [isPlaying, isControlled]);
71
+ const toggle = React__namespace.useCallback(() => {
72
+ const next = !playingRef.current;
73
+ if (!isControlledRef.current) setInternalPlaying(next);
74
+ if (next) onPlay?.();
75
+ else onPause?.();
76
+ }, [onPlay, onPause]);
77
+ const seekTo = React__namespace.useCallback(
78
+ (time) => {
79
+ if (displayDuration === void 0) return;
80
+ const clamped = Math.min(Math.max(time, 0), displayDuration);
81
+ setCurrentTime(clamped);
82
+ const audio = audioRef.current;
83
+ if (audio) audio.currentTime = clamped;
84
+ },
85
+ [displayDuration]
86
+ );
87
+ const setSeeking = React__namespace.useCallback((seeking) => {
88
+ seekingRef.current = seeking;
89
+ }, []);
90
+ const progress = displayDuration && displayDuration > 0 ? Math.min(Math.max(currentTime / displayDuration, 0), 1) : 0;
91
+ const audioHandlers = React__namespace.useMemo(
92
+ () => ({
93
+ onLoadedMetadata: (event) => {
94
+ const nativeDuration = event.currentTarget.duration;
95
+ setMediaDuration(Number.isFinite(nativeDuration) ? nativeDuration : void 0);
96
+ },
97
+ onTimeUpdate: (event) => {
98
+ if (seekingRef.current) return;
99
+ setCurrentTime(event.currentTarget.currentTime);
100
+ },
101
+ onEnded: () => {
102
+ setCurrentTime(0);
103
+ if (!isControlledRef.current) setInternalPlaying(false);
104
+ onEnded?.();
105
+ }
106
+ }),
107
+ [onEnded]
108
+ );
109
+ return {
110
+ audioRef,
111
+ isPlaying,
112
+ toggle,
113
+ currentTime,
114
+ displayDuration,
115
+ progress,
116
+ hasStarted,
117
+ seekTo,
118
+ setSeeking,
119
+ audioHandlers
120
+ };
121
+ }
122
+ exports.useAudioPlayback = useAudioPlayback;
123
+ //# sourceMappingURL=useAudioPlayback.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAudioPlayback.cjs","sources":["../../../src/utils/useAudioPlayback.ts"],"sourcesContent":["import * as React from \"react\";\n\n/** Options for {@link useAudioPlayback}. */\nexport interface UseAudioPlaybackOptions {\n /** URL of the audio to play. When absent, the hook stays idle (no media element attached). */\n src?: string;\n /** Fallback total duration (seconds) used until the media's own metadata loads. */\n duration?: number;\n /** Whether playback is active (controlled). */\n playing?: boolean;\n /** Initial playing state (uncontrolled). @default false */\n defaultPlaying?: boolean;\n /** Called when playback starts (via the toggle). */\n onPlay?: () => void;\n /** Called when playback pauses (via the toggle, a failed play(), or a src swap). */\n onPause?: () => void;\n /** Called when playback reaches the end of the media. */\n onEnded?: () => void;\n}\n\n/** Handlers to spread onto the backing `<audio>` element. */\nexport interface AudioElementHandlers {\n onLoadedMetadata: (event: React.SyntheticEvent<HTMLAudioElement>) => void;\n onTimeUpdate: (event: React.SyntheticEvent<HTMLAudioElement>) => void;\n onEnded: () => void;\n}\n\n/** Result of {@link useAudioPlayback}. */\nexport interface UseAudioPlaybackResult {\n /** Attach to the backing `<audio>` element. */\n audioRef: React.RefObject<HTMLAudioElement | null>;\n /** Current play/pause state (respects controlled `playing`). */\n isPlaying: boolean;\n /** Toggle play/pause, firing `onPlay`/`onPause`. */\n toggle: () => void;\n /** Current playback position in seconds. */\n currentTime: number;\n /** Media duration once known, else the `duration` fallback. */\n displayDuration: number | undefined;\n /** Playback progress, 0–1. */\n progress: number;\n /** Whether playback has begun (playing or scrubbed past 0). */\n hasStarted: boolean;\n /** Seek to a time in seconds (clamped to the duration). */\n seekTo: (time: number) => void;\n /** Suppress `timeupdate`-driven position updates while a manual scrub is in progress. */\n setSeeking: (seeking: boolean) => void;\n /** Handlers to spread onto the `<audio>` element. */\n audioHandlers: AudioElementHandlers;\n}\n\n/**\n * Drives a single `<audio>` element: controlled/uncontrolled play/pause, live\n * position + duration, seeking, and src-swap resets. Extracted from\n * {@link AudioPlayer} so waveform players (e.g. {@link VoiceNote}) can share the\n * same playback engine without duplicating the effect wiring.\n */\nexport function useAudioPlayback({\n src,\n duration,\n playing: controlledPlaying,\n defaultPlaying = false,\n onPlay,\n onPause,\n onEnded,\n}: UseAudioPlaybackOptions): UseAudioPlaybackResult {\n const audioRef = React.useRef<HTMLAudioElement>(null);\n\n const [internalPlaying, setInternalPlaying] = React.useState(defaultPlaying);\n const isControlled = controlledPlaying !== undefined;\n const isPlaying = isControlled ? controlledPlaying : internalPlaying;\n\n const [currentTime, setCurrentTime] = React.useState(0);\n const [mediaDuration, setMediaDuration] = React.useState<number | undefined>(undefined);\n const seekingRef = React.useRef(false);\n\n const displayDuration = mediaDuration ?? duration;\n const hasStarted = isPlaying || currentTime > 0;\n\n // Latest-value refs so the src-change effect can react to a src swap without\n // re-running whenever playing/isControlled/onPause change.\n const playingRef = React.useRef(isPlaying);\n playingRef.current = isPlaying;\n const isControlledRef = React.useRef(isControlled);\n isControlledRef.current = isControlled;\n const onPauseRef = React.useRef(onPause);\n onPauseRef.current = onPause;\n\n // Reset transient playback state on source change; skip the initial mount and,\n // if playback was active, stop it (the browser drops playback on a src swap).\n const isFirstSrcRenderRef = React.useRef(true);\n // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally keyed on src to reset transient state on a source swap\n React.useEffect(() => {\n if (isFirstSrcRenderRef.current) {\n isFirstSrcRenderRef.current = false;\n return;\n }\n setCurrentTime(0);\n setMediaDuration(undefined);\n if (playingRef.current) {\n if (!isControlledRef.current) setInternalPlaying(false);\n onPauseRef.current?.();\n }\n }, [src]);\n\n // Sync the controlled/uncontrolled playing state to the media element.\n React.useEffect(() => {\n const audio = audioRef.current;\n if (!audio) return;\n if (isPlaying) {\n audio.play().catch(() => {\n if (!isControlled) setInternalPlaying(false);\n onPauseRef.current?.();\n });\n } else {\n audio.pause();\n }\n }, [isPlaying, isControlled]);\n\n const toggle = React.useCallback(() => {\n const next = !playingRef.current;\n if (!isControlledRef.current) setInternalPlaying(next);\n if (next) onPlay?.();\n else onPause?.();\n }, [onPlay, onPause]);\n\n const seekTo = React.useCallback(\n (time: number) => {\n if (displayDuration === undefined) return;\n const clamped = Math.min(Math.max(time, 0), displayDuration);\n setCurrentTime(clamped);\n const audio = audioRef.current;\n if (audio) audio.currentTime = clamped;\n },\n [displayDuration],\n );\n\n const setSeeking = React.useCallback((seeking: boolean) => {\n seekingRef.current = seeking;\n }, []);\n\n const progress =\n displayDuration && displayDuration > 0\n ? Math.min(Math.max(currentTime / displayDuration, 0), 1)\n : 0;\n\n const audioHandlers = React.useMemo<AudioElementHandlers>(\n () => ({\n onLoadedMetadata: (event) => {\n const nativeDuration = event.currentTarget.duration;\n setMediaDuration(Number.isFinite(nativeDuration) ? nativeDuration : undefined);\n },\n onTimeUpdate: (event) => {\n if (seekingRef.current) return;\n setCurrentTime(event.currentTarget.currentTime);\n },\n onEnded: () => {\n setCurrentTime(0);\n if (!isControlledRef.current) setInternalPlaying(false);\n onEnded?.();\n },\n }),\n [onEnded],\n );\n\n return {\n audioRef,\n isPlaying,\n toggle,\n currentTime,\n displayDuration,\n progress,\n hasStarted,\n seekTo,\n setSeeking,\n audioHandlers,\n };\n}\n"],"names":["React"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const React = require("react");
5
+ function _interopNamespaceDefault(e) {
6
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
7
+ if (e) {
8
+ for (const k in e) {
9
+ if (k !== "default") {
10
+ const d = Object.getOwnPropertyDescriptor(e, k);
11
+ Object.defineProperty(n, k, d.get ? d : {
12
+ enumerable: true,
13
+ get: () => e[k]
14
+ });
15
+ }
16
+ }
17
+ }
18
+ n.default = e;
19
+ return Object.freeze(n);
20
+ }
21
+ const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
22
+ function useFittedBarCount(trackRef, { barWidthPx, gapPx, fallback }) {
23
+ const [barCount, setBarCount] = React__namespace.useState(fallback);
24
+ React__namespace.useEffect(() => {
25
+ const track = trackRef.current;
26
+ if (!track) return;
27
+ const updateFromWidth = (width) => {
28
+ if (width <= 0) return;
29
+ setBarCount(Math.max(1, Math.floor((width + gapPx) / (barWidthPx + gapPx))));
30
+ };
31
+ updateFromWidth(track.getBoundingClientRect().width);
32
+ const observer = new ResizeObserver((entries) => {
33
+ const entry = entries[0];
34
+ if (entry) updateFromWidth(entry.contentRect.width);
35
+ });
36
+ observer.observe(track);
37
+ return () => observer.disconnect();
38
+ }, [trackRef, barWidthPx, gapPx]);
39
+ return barCount;
40
+ }
41
+ exports.useFittedBarCount = useFittedBarCount;
42
+ //# sourceMappingURL=useFittedBarCount.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFittedBarCount.cjs","sources":["../../../src/utils/useFittedBarCount.ts"],"sourcesContent":["import * as React from \"react\";\n\n/** Options for {@link useFittedBarCount}. */\nexport interface UseFittedBarCountOptions {\n /** Rendered width of a single bar, in pixels. */\n barWidthPx: number;\n /** Gap between bars, in pixels. */\n gapPx: number;\n /** Bar count used before the track is measured (SSR, jsdom, or zero-width layout). */\n fallback: number;\n}\n\n/**\n * Measures a waveform track and returns how many fixed-width bars fit across it,\n * updating on resize. Lets callers resample their peaks to the *visible* bar\n * count so overflow bars aren't rendered (and, crucially, aren't counted when\n * computing playback progress).\n */\nexport function useFittedBarCount(\n trackRef: React.RefObject<HTMLElement | null>,\n { barWidthPx, gapPx, fallback }: UseFittedBarCountOptions,\n): number {\n const [barCount, setBarCount] = React.useState(fallback);\n\n React.useEffect(() => {\n const track = trackRef.current;\n if (!track) return;\n\n const updateFromWidth = (width: number) => {\n if (width <= 0) return;\n setBarCount(Math.max(1, Math.floor((width + gapPx) / (barWidthPx + gapPx))));\n };\n\n updateFromWidth(track.getBoundingClientRect().width);\n\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) updateFromWidth(entry.contentRect.width);\n });\n observer.observe(track);\n return () => observer.disconnect();\n }, [trackRef, barWidthPx, gapPx]);\n\n return barCount;\n}\n"],"names":["React"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAkBO,SAAS,kBACd,UACA,EAAE,YAAY,OAAO,YACb;AACR,QAAM,CAAC,UAAU,WAAW,IAAIA,iBAAM,SAAS,QAAQ;AAEvDA,mBAAM,UAAU,MAAM;AACpB,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AAEZ,UAAM,kBAAkB,CAAC,UAAkB;AACzC,UAAI,SAAS,EAAG;AAChB,kBAAY,KAAK,IAAI,GAAG,KAAK,OAAO,QAAQ,UAAU,aAAa,MAAM,CAAC,CAAC;AAAA,IAC7E;AAEA,oBAAgB,MAAM,sBAAA,EAAwB,KAAK;AAEnD,UAAM,WAAW,IAAI,eAAe,CAAC,YAAY;AAC/C,YAAM,QAAQ,QAAQ,CAAC;AACvB,UAAI,MAAO,iBAAgB,MAAM,YAAY,KAAK;AAAA,IACpD,CAAC;AACD,aAAS,QAAQ,KAAK;AACtB,WAAO,MAAM,SAAS,WAAA;AAAA,EACxB,GAAG,CAAC,UAAU,YAAY,KAAK,CAAC;AAEhC,SAAO;AACT;;"}
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const React = require("react");
5
+ const audioWaveform = require("./audioWaveform.cjs");
6
+ function _interopNamespaceDefault(e) {
7
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
8
+ if (e) {
9
+ for (const k in e) {
10
+ if (k !== "default") {
11
+ const d = Object.getOwnPropertyDescriptor(e, k);
12
+ Object.defineProperty(n, k, d.get ? d : {
13
+ enumerable: true,
14
+ get: () => e[k]
15
+ });
16
+ }
17
+ }
18
+ }
19
+ n.default = e;
20
+ return Object.freeze(n);
21
+ }
22
+ const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
23
+ function useWaveformPeaks(src, count = audioWaveform.RAW_PEAK_COUNT) {
24
+ const [peaks, setPeaks] = React__namespace.useState(
25
+ () => src ? audioWaveform.generateFallbackPeaks(src, count) : []
26
+ );
27
+ React__namespace.useEffect(() => {
28
+ if (!src) {
29
+ setPeaks([]);
30
+ return;
31
+ }
32
+ setPeaks(audioWaveform.generateFallbackPeaks(src, count));
33
+ let cancelled = false;
34
+ const abortController = new AbortController();
35
+ audioWaveform.decodeAudioPeaks(src, abortController.signal).then((decoded) => {
36
+ if (!cancelled) setPeaks(decoded);
37
+ }).catch(() => {
38
+ if (!cancelled) setPeaks(audioWaveform.generateFallbackPeaks(src, count));
39
+ });
40
+ return () => {
41
+ cancelled = true;
42
+ abortController.abort();
43
+ };
44
+ }, [src, count]);
45
+ return peaks;
46
+ }
47
+ exports.useWaveformPeaks = useWaveformPeaks;
48
+ //# sourceMappingURL=useWaveformPeaks.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWaveformPeaks.cjs","sources":["../../../src/utils/useWaveformPeaks.ts"],"sourcesContent":["import * as React from \"react\";\nimport { decodeAudioPeaks, generateFallbackPeaks, RAW_PEAK_COUNT } from \"./audioWaveform\";\n\n/**\n * Decodes `src` into waveform peak amplitudes (0–1). Seeds a deterministic\n * fallback immediately (stable across renders/SSR) and swaps in the real\n * decoded peaks once WebAudio resolves, falling back again on any failure.\n * Returns an empty array when `src` is absent.\n */\nexport function useWaveformPeaks(\n src: string | undefined,\n count: number = RAW_PEAK_COUNT,\n): number[] {\n const [peaks, setPeaks] = React.useState<number[]>(() =>\n src ? generateFallbackPeaks(src, count) : [],\n );\n\n React.useEffect(() => {\n if (!src) {\n setPeaks([]);\n return;\n }\n\n setPeaks(generateFallbackPeaks(src, count));\n\n let cancelled = false;\n const abortController = new AbortController();\n decodeAudioPeaks(src, abortController.signal)\n .then((decoded) => {\n if (!cancelled) setPeaks(decoded);\n })\n .catch(() => {\n if (!cancelled) setPeaks(generateFallbackPeaks(src, count));\n });\n\n return () => {\n cancelled = true;\n abortController.abort();\n };\n }, [src, count]);\n\n return peaks;\n}\n"],"names":["RAW_PEAK_COUNT","React","generateFallbackPeaks","decodeAudioPeaks"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AASO,SAAS,iBACd,KACA,QAAgBA,8BACN;AACV,QAAM,CAAC,OAAO,QAAQ,IAAIC,iBAAM;AAAA,IAAmB,MACjD,MAAMC,cAAAA,sBAAsB,KAAK,KAAK,IAAI,CAAA;AAAA,EAAC;AAG7CD,mBAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAK;AACR,eAAS,CAAA,CAAE;AACX;AAAA,IACF;AAEA,aAASC,cAAAA,sBAAsB,KAAK,KAAK,CAAC;AAE1C,QAAI,YAAY;AAChB,UAAM,kBAAkB,IAAI,gBAAA;AAC5BC,kBAAAA,iBAAiB,KAAK,gBAAgB,MAAM,EACzC,KAAK,CAAC,YAAY;AACjB,UAAI,CAAC,UAAW,UAAS,OAAO;AAAA,IAClC,CAAC,EACA,MAAM,MAAM;AACX,UAAI,CAAC,UAAW,UAASD,cAAAA,sBAAsB,KAAK,KAAK,CAAC;AAAA,IAC5D,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AACZ,sBAAgB,MAAA;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,KAAK,KAAK,CAAC;AAEf,SAAO;AACT;;"}
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const React = require("react");
5
+ const audioWaveform = require("./audioWaveform.cjs");
6
+ function _interopNamespaceDefault(e) {
7
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
8
+ if (e) {
9
+ for (const k in e) {
10
+ if (k !== "default") {
11
+ const d = Object.getOwnPropertyDescriptor(e, k);
12
+ Object.defineProperty(n, k, d.get ? d : {
13
+ enumerable: true,
14
+ get: () => e[k]
15
+ });
16
+ }
17
+ }
18
+ }
19
+ n.default = e;
20
+ return Object.freeze(n);
21
+ }
22
+ const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
23
+ const SEEK_STEP_SECONDS = 1;
24
+ const SEEK_STEP_LARGE_SECONDS = 5;
25
+ const STEP_BY_KEY = {
26
+ ArrowRight: SEEK_STEP_SECONDS,
27
+ ArrowUp: SEEK_STEP_SECONDS,
28
+ ArrowLeft: -SEEK_STEP_SECONDS,
29
+ ArrowDown: -SEEK_STEP_SECONDS,
30
+ PageUp: SEEK_STEP_LARGE_SECONDS,
31
+ PageDown: -SEEK_STEP_LARGE_SECONDS
32
+ };
33
+ function useWaveformSeek({
34
+ trackRef,
35
+ displayDuration,
36
+ currentTime,
37
+ seekTo,
38
+ setSeeking,
39
+ label = "Seek"
40
+ }) {
41
+ const [isDragging, setIsDragging] = React__namespace.useState(false);
42
+ const seekFromClientX = React__namespace.useCallback(
43
+ (clientX) => {
44
+ const track = trackRef.current;
45
+ if (!track || displayDuration === void 0) return;
46
+ const rect = track.getBoundingClientRect();
47
+ if (rect.width <= 0) return;
48
+ const fraction = Math.min(Math.max((clientX - rect.left) / rect.width, 0), 1);
49
+ seekTo(fraction * displayDuration);
50
+ },
51
+ [trackRef, displayDuration, seekTo]
52
+ );
53
+ React__namespace.useEffect(() => {
54
+ if (!isDragging) return;
55
+ const handleMove = (event) => seekFromClientX(event.clientX);
56
+ const handleUp = () => {
57
+ setIsDragging(false);
58
+ setSeeking(false);
59
+ };
60
+ window.addEventListener("pointermove", handleMove);
61
+ window.addEventListener("pointerup", handleUp);
62
+ return () => {
63
+ window.removeEventListener("pointermove", handleMove);
64
+ window.removeEventListener("pointerup", handleUp);
65
+ };
66
+ }, [isDragging, seekFromClientX, setSeeking]);
67
+ const onPointerDown = (event) => {
68
+ seekFromClientX(event.clientX);
69
+ setSeeking(true);
70
+ setIsDragging(true);
71
+ };
72
+ const onKeyDown = (event) => {
73
+ if (displayDuration === void 0) return;
74
+ const delta = STEP_BY_KEY[event.key];
75
+ if (delta !== void 0) {
76
+ event.preventDefault();
77
+ seekTo(currentTime + delta);
78
+ } else if (event.key === "Home") {
79
+ event.preventDefault();
80
+ seekTo(0);
81
+ } else if (event.key === "End") {
82
+ event.preventDefault();
83
+ seekTo(displayDuration);
84
+ }
85
+ };
86
+ return {
87
+ role: "slider",
88
+ tabIndex: 0,
89
+ "aria-label": label,
90
+ "aria-orientation": "horizontal",
91
+ "aria-valuemin": 0,
92
+ "aria-valuemax": displayDuration,
93
+ "aria-valuenow": Math.round(currentTime),
94
+ "aria-valuetext": displayDuration !== void 0 ? `${audioWaveform.formatTime(currentTime)} of ${audioWaveform.formatTime(displayDuration)}` : void 0,
95
+ onPointerDown,
96
+ onKeyDown
97
+ };
98
+ }
99
+ exports.useWaveformSeek = useWaveformSeek;
100
+ //# sourceMappingURL=useWaveformSeek.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWaveformSeek.cjs","sources":["../../../src/utils/useWaveformSeek.ts"],"sourcesContent":["import * as React from \"react\";\nimport { formatTime } from \"./audioWaveform\";\n\n/** Seconds moved per Arrow key press while seeking. */\nconst SEEK_STEP_SECONDS = 1;\n/** Seconds moved per Page Up/Down press while seeking. */\nconst SEEK_STEP_LARGE_SECONDS = 5;\n\n/** Signed seek delta (seconds) for each stepping key. */\nconst STEP_BY_KEY: Record<string, number | undefined> = {\n ArrowRight: SEEK_STEP_SECONDS,\n ArrowUp: SEEK_STEP_SECONDS,\n ArrowLeft: -SEEK_STEP_SECONDS,\n ArrowDown: -SEEK_STEP_SECONDS,\n PageUp: SEEK_STEP_LARGE_SECONDS,\n PageDown: -SEEK_STEP_LARGE_SECONDS,\n};\n\n/** Options for {@link useWaveformSeek}. */\nexport interface UseWaveformSeekOptions {\n /** Ref to the seekable track element (its width maps pointer position → time). */\n trackRef: React.RefObject<HTMLElement | null>;\n /** Total duration in seconds, or `undefined` until known. */\n displayDuration: number | undefined;\n /** Current playback position in seconds. */\n currentTime: number;\n /** Seek to a time in seconds. */\n seekTo: (time: number) => void;\n /** Suppress position updates while a manual scrub is in progress. */\n setSeeking: (seeking: boolean) => void;\n /** Accessible name for the slider. @default \"Seek\" */\n label?: string;\n}\n\n/** ARIA + event props to spread onto the seekable track element. */\nexport interface WaveformSeekProps {\n role: \"slider\";\n tabIndex: 0;\n \"aria-label\": string;\n \"aria-orientation\": \"horizontal\";\n \"aria-valuemin\": 0;\n \"aria-valuemax\": number | undefined;\n \"aria-valuenow\": number;\n \"aria-valuetext\": string | undefined;\n onPointerDown: (event: React.PointerEvent<HTMLElement>) => void;\n onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void;\n}\n\n/**\n * Turns a waveform track into a keyboard- and pointer-seekable slider. Handles\n * click/drag position math (via window listeners so drags can leave the element)\n * and Arrow/Page/Home/End stepping, and returns ARIA slider props to spread onto\n * the track. Shared by waveform players so seek behaviour isn't reimplemented.\n */\nexport function useWaveformSeek({\n trackRef,\n displayDuration,\n currentTime,\n seekTo,\n setSeeking,\n label = \"Seek\",\n}: UseWaveformSeekOptions): WaveformSeekProps {\n const [isDragging, setIsDragging] = React.useState(false);\n\n const seekFromClientX = React.useCallback(\n (clientX: number) => {\n const track = trackRef.current;\n if (!track || displayDuration === undefined) return;\n const rect = track.getBoundingClientRect();\n if (rect.width <= 0) return;\n const fraction = Math.min(Math.max((clientX - rect.left) / rect.width, 0), 1);\n seekTo(fraction * displayDuration);\n },\n [trackRef, displayDuration, seekTo],\n );\n\n React.useEffect(() => {\n if (!isDragging) return;\n const handleMove = (event: PointerEvent) => seekFromClientX(event.clientX);\n const handleUp = () => {\n setIsDragging(false);\n setSeeking(false);\n };\n window.addEventListener(\"pointermove\", handleMove);\n window.addEventListener(\"pointerup\", handleUp);\n return () => {\n window.removeEventListener(\"pointermove\", handleMove);\n window.removeEventListener(\"pointerup\", handleUp);\n };\n }, [isDragging, seekFromClientX, setSeeking]);\n\n const onPointerDown = (event: React.PointerEvent<HTMLElement>) => {\n seekFromClientX(event.clientX);\n setSeeking(true);\n setIsDragging(true);\n };\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {\n if (displayDuration === undefined) return;\n const delta = STEP_BY_KEY[event.key];\n if (delta !== undefined) {\n event.preventDefault();\n seekTo(currentTime + delta);\n } else if (event.key === \"Home\") {\n event.preventDefault();\n seekTo(0);\n } else if (event.key === \"End\") {\n event.preventDefault();\n seekTo(displayDuration);\n }\n };\n\n return {\n role: \"slider\",\n tabIndex: 0,\n \"aria-label\": label,\n \"aria-orientation\": \"horizontal\",\n \"aria-valuemin\": 0,\n \"aria-valuemax\": displayDuration,\n \"aria-valuenow\": Math.round(currentTime),\n \"aria-valuetext\":\n displayDuration !== undefined\n ? `${formatTime(currentTime)} of ${formatTime(displayDuration)}`\n : undefined,\n onPointerDown,\n onKeyDown,\n };\n}\n"],"names":["React","formatTime"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAIA,MAAM,oBAAoB;AAE1B,MAAM,0BAA0B;AAGhC,MAAM,cAAkD;AAAA,EACtD,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,WAAW,CAAC;AAAA,EACZ,WAAW,CAAC;AAAA,EACZ,QAAQ;AAAA,EACR,UAAU,CAAC;AACb;AAsCO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAA8C;AAC5C,QAAM,CAAC,YAAY,aAAa,IAAIA,iBAAM,SAAS,KAAK;AAExD,QAAM,kBAAkBA,iBAAM;AAAA,IAC5B,CAAC,YAAoB;AACnB,YAAM,QAAQ,SAAS;AACvB,UAAI,CAAC,SAAS,oBAAoB,OAAW;AAC7C,YAAM,OAAO,MAAM,sBAAA;AACnB,UAAI,KAAK,SAAS,EAAG;AACrB,YAAM,WAAW,KAAK,IAAI,KAAK,KAAK,UAAU,KAAK,QAAQ,KAAK,OAAO,CAAC,GAAG,CAAC;AAC5E,aAAO,WAAW,eAAe;AAAA,IACnC;AAAA,IACA,CAAC,UAAU,iBAAiB,MAAM;AAAA,EAAA;AAGpCA,mBAAM,UAAU,MAAM;AACpB,QAAI,CAAC,WAAY;AACjB,UAAM,aAAa,CAAC,UAAwB,gBAAgB,MAAM,OAAO;AACzE,UAAM,WAAW,MAAM;AACrB,oBAAc,KAAK;AACnB,iBAAW,KAAK;AAAA,IAClB;AACA,WAAO,iBAAiB,eAAe,UAAU;AACjD,WAAO,iBAAiB,aAAa,QAAQ;AAC7C,WAAO,MAAM;AACX,aAAO,oBAAoB,eAAe,UAAU;AACpD,aAAO,oBAAoB,aAAa,QAAQ;AAAA,IAClD;AAAA,EACF,GAAG,CAAC,YAAY,iBAAiB,UAAU,CAAC;AAE5C,QAAM,gBAAgB,CAAC,UAA2C;AAChE,oBAAgB,MAAM,OAAO;AAC7B,eAAW,IAAI;AACf,kBAAc,IAAI;AAAA,EACpB;AAEA,QAAM,YAAY,CAAC,UAA4C;AAC7D,QAAI,oBAAoB,OAAW;AACnC,UAAM,QAAQ,YAAY,MAAM,GAAG;AACnC,QAAI,UAAU,QAAW;AACvB,YAAM,eAAA;AACN,aAAO,cAAc,KAAK;AAAA,IAC5B,WAAW,MAAM,QAAQ,QAAQ;AAC/B,YAAM,eAAA;AACN,aAAO,CAAC;AAAA,IACV,WAAW,MAAM,QAAQ,OAAO;AAC9B,YAAM,eAAA;AACN,aAAO,eAAe;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,cAAc;AAAA,IACd,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,IACjB,iBAAiB,KAAK,MAAM,WAAW;AAAA,IACvC,kBACE,oBAAoB,SAChB,GAAGC,cAAAA,WAAW,WAAW,CAAC,OAAOA,cAAAA,WAAW,eAAe,CAAC,KAC5D;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEJ;;"}