@djangocfg/ui-tools 2.1.404 → 2.1.408
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -11
- package/dist/file-icon/index.cjs +449 -61
- package/dist/file-icon/index.cjs.map +1 -1
- package/dist/file-icon/index.d.cts +56 -18
- package/dist/file-icon/index.d.ts +56 -18
- package/dist/file-icon/index.mjs +448 -62
- package/dist/file-icon/index.mjs.map +1 -1
- package/dist/tree/index.cjs +49 -22
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.d.cts +9 -3
- package/dist/tree/index.d.ts +9 -3
- package/dist/tree/index.mjs +49 -22
- package/dist/tree/index.mjs.map +1 -1
- package/dist/{types-B_zhyAqR.d.cts → types-eEu8SeiQ.d.cts} +4 -0
- package/dist/{types-B_zhyAqR.d.ts → types-eEu8SeiQ.d.ts} +4 -0
- package/package.json +13 -16
- package/src/components/FloatingToolbar/index.tsx +37 -3
- package/src/lib/page-snapshot/__tests__/capture-integration.test.ts +85 -0
- package/src/lib/page-snapshot/__tests__/engine.test.ts +36 -0
- package/src/lib/page-snapshot/__tests__/redaction-integration.test.ts +99 -0
- package/src/lib/page-snapshot/__tests__/tokens.test.ts +17 -0
- package/src/lib/page-snapshot/capture/__tests__/budget.test.ts +49 -0
- package/src/lib/page-snapshot/capture/__tests__/chrome-filter.test.ts +47 -0
- package/src/lib/page-snapshot/capture/__tests__/fold.test.ts +66 -0
- package/src/lib/page-snapshot/capture/__tests__/scope.test.ts +74 -0
- package/src/lib/page-snapshot/capture/__tests__/walk.test.ts +129 -0
- package/src/lib/page-snapshot/capture/accessible-name.ts +73 -0
- package/src/lib/page-snapshot/capture/budget.ts +95 -0
- package/src/lib/page-snapshot/capture/chrome-filter.ts +81 -0
- package/src/lib/page-snapshot/capture/classify.ts +111 -0
- package/src/lib/page-snapshot/capture/dom-utils.ts +111 -0
- package/src/lib/page-snapshot/capture/fold.ts +96 -0
- package/src/lib/page-snapshot/capture/scope.ts +169 -0
- package/src/lib/page-snapshot/capture/walk.ts +250 -0
- package/src/lib/page-snapshot/cst/__tests__/serialize.test.ts +50 -0
- package/src/lib/page-snapshot/cst/directives.ts +47 -0
- package/src/lib/page-snapshot/cst/payload.ts +50 -0
- package/src/lib/page-snapshot/cst/serialize.ts +84 -0
- package/src/lib/page-snapshot/cst/types.ts +115 -0
- package/src/lib/page-snapshot/engine.ts +176 -0
- package/src/lib/page-snapshot/index.ts +93 -0
- package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +72 -0
- package/src/lib/page-snapshot/react/PageSnapshotPreview.tsx +78 -0
- package/src/lib/page-snapshot/react/__tests__/PageSnapshotChip.test.tsx +54 -0
- package/src/lib/page-snapshot/react/__tests__/provider.test.tsx +103 -0
- package/src/lib/page-snapshot/react/__tests__/use-page-snapshot-toggle.test.tsx +62 -0
- package/src/lib/page-snapshot/react/provider.tsx +162 -0
- package/src/lib/page-snapshot/react/use-page-snapshot-toggle.ts +47 -0
- package/src/lib/page-snapshot/react/use-page-snapshot.ts +67 -0
- package/src/lib/page-snapshot/redaction/__tests__/audit.test.ts +25 -0
- package/src/lib/page-snapshot/redaction/__tests__/heuristics.test.ts +73 -0
- package/src/lib/page-snapshot/redaction/__tests__/luhn.test.ts +26 -0
- package/src/lib/page-snapshot/redaction/__tests__/patterns.test.ts +60 -0
- package/src/lib/page-snapshot/redaction/audit.ts +58 -0
- package/src/lib/page-snapshot/redaction/heuristics.ts +75 -0
- package/src/lib/page-snapshot/redaction/index.ts +75 -0
- package/src/lib/page-snapshot/redaction/luhn.ts +25 -0
- package/src/lib/page-snapshot/redaction/patterns.ts +111 -0
- package/src/lib/page-snapshot/refs/__tests__/registry.test.ts +24 -0
- package/src/lib/page-snapshot/refs/registry.ts +46 -0
- package/src/lib/page-snapshot/staleness/__tests__/hash.test.ts +34 -0
- package/src/lib/page-snapshot/staleness/hash.ts +20 -0
- package/src/lib/page-snapshot/tokens.ts +15 -0
- package/src/tools/AudioPlayer/context/PlayerProvider.tsx +13 -14
- package/src/tools/AudioPlayer/hooks/useAudioElementEvents.ts +55 -6
- package/src/tools/AudioPlayer/lazy.tsx +13 -27
- package/src/tools/AudioPlayer/parts/Meta/TimeDisplay.tsx +2 -5
- package/src/tools/Chat/README.md +267 -39
- package/src/tools/Chat/composer/Composer.tsx +471 -0
- package/src/tools/Chat/composer/ComposerActionBar.tsx +65 -0
- package/src/tools/Chat/composer/ComposerBanner.tsx +128 -0
- package/src/tools/Chat/composer/ComposerButton.tsx +64 -0
- package/src/tools/Chat/composer/ComposerFooter.tsx +90 -0
- package/src/tools/Chat/composer/ComposerMenuButton.tsx +62 -0
- package/src/tools/Chat/composer/ComposerModelPicker.tsx +104 -0
- package/src/tools/Chat/composer/ComposerRichTextarea.tsx +88 -0
- package/src/tools/Chat/composer/ComposerToolPill.tsx +95 -0
- package/src/tools/Chat/composer/index.ts +45 -0
- package/src/tools/Chat/composer/size-context.tsx +26 -0
- package/src/tools/Chat/composer/types.ts +143 -0
- package/src/tools/Chat/composer/useComposerActions.tsx +164 -0
- package/src/tools/Chat/context/ChatProvider.tsx +54 -3
- package/src/tools/Chat/core/__tests__/metadata.test.ts +69 -0
- package/src/tools/Chat/core/index.ts +23 -1
- package/src/tools/Chat/core/markdown.ts +1 -1
- package/src/tools/Chat/core/metadata.ts +47 -0
- package/src/tools/Chat/core/payload-dispatch.ts +1 -1
- package/src/tools/Chat/core/transport/http.ts +71 -32
- package/src/tools/Chat/core/transport/sse.ts +18 -10
- package/src/tools/Chat/highlight/HighlightOverlay.tsx +101 -0
- package/src/tools/Chat/highlight/README.md +103 -0
- package/src/tools/Chat/highlight/SpotlightCanvas.tsx +153 -0
- package/src/tools/Chat/highlight/__tests__/HighlightOverlay.test.tsx +112 -0
- package/src/tools/Chat/highlight/__tests__/resolveRef.test.ts +55 -0
- package/src/tools/Chat/highlight/index.ts +21 -0
- package/src/tools/Chat/highlight/resolveRef.ts +42 -0
- package/src/tools/Chat/highlight/types.ts +49 -0
- package/src/tools/Chat/highlight/useHighlightTargets.ts +128 -0
- package/src/tools/Chat/hooks/index.ts +0 -5
- package/src/tools/Chat/hooks/useAutoFocusOnStreamEnd.ts +28 -47
- package/src/tools/Chat/hooks/useChat.ts +47 -14
- package/src/tools/Chat/hooks/useChatComposer.ts +2 -2
- package/src/tools/Chat/hooks/useChatLayout.ts +1 -1
- package/src/tools/Chat/hooks/useStreamEndFocus.ts +54 -0
- package/src/tools/Chat/index.ts +25 -219
- package/src/tools/Chat/launcher/ChatDock.tsx +1 -1
- package/src/tools/Chat/launcher/ChatLauncher.tsx +1 -1
- package/src/tools/Chat/launcher/{ChatHeader.tsx → header/ChatHeader.tsx} +24 -11
- package/src/tools/Chat/launcher/{ChatHeaderActionButton.tsx → header/ChatHeaderActionButton.tsx} +34 -3
- package/src/tools/Chat/launcher/{ChatHeaderLanguageButton.tsx → header/ChatHeaderLanguageButton.tsx} +2 -2
- package/src/tools/Chat/launcher/{ChatHeaderModeToggle.tsx → header/ChatHeaderModeToggle.tsx} +1 -1
- package/src/tools/Chat/launcher/{ChatHeaderResetButton.tsx → header/ChatHeaderResetButton.tsx} +2 -1
- package/src/tools/Chat/launcher/{HeaderSlots.tsx → header/HeaderSlots.tsx} +3 -3
- package/src/tools/Chat/launcher/header/index.ts +26 -0
- package/src/tools/Chat/launcher/index.ts +3 -10
- package/src/tools/Chat/lazy.tsx +38 -284
- package/src/tools/Chat/{components → messages}/MessageBubble.tsx +58 -5
- package/src/tools/Chat/{components → messages}/MessageList.tsx +8 -25
- package/src/tools/Chat/messages/blocks/MessageBlocks.tsx +131 -0
- package/src/tools/Chat/messages/blocks/builtin.tsx +91 -0
- package/src/tools/Chat/messages/blocks/index.ts +12 -0
- package/src/tools/Chat/messages/blocks/registry.tsx +42 -0
- package/src/tools/Chat/messages/blocks/renderers/AudioBlock.tsx +20 -0
- package/src/tools/Chat/messages/blocks/renderers/CodeBlock.tsx +19 -0
- package/src/tools/Chat/messages/blocks/renderers/GalleryBlock.tsx +26 -0
- package/src/tools/Chat/messages/blocks/renderers/ImageBlock.tsx +27 -0
- package/src/tools/Chat/messages/blocks/renderers/JsonBlock.tsx +12 -0
- package/src/tools/Chat/messages/blocks/renderers/LottieBlock.tsx +11 -0
- package/src/tools/Chat/messages/blocks/renderers/MapBlock.tsx +36 -0
- package/src/tools/Chat/messages/blocks/renderers/MermaidBlock.tsx +11 -0
- package/src/tools/Chat/messages/blocks/renderers/VideoBlock.tsx +24 -0
- package/src/tools/Chat/messages/blocks/renderers/types.ts +8 -0
- package/src/tools/Chat/{components → messages}/index.ts +11 -5
- package/src/tools/Chat/public.ts +212 -0
- package/src/tools/Chat/shell/ChatRoot.tsx +326 -0
- package/src/tools/Chat/{components → shell}/EmptyState.tsx +4 -2
- package/src/tools/Chat/shell/index.ts +15 -0
- package/src/tools/Chat/types/block.ts +120 -0
- package/src/tools/Chat/types/config.ts +0 -5
- package/src/tools/Chat/types/index.ts +17 -0
- package/src/tools/Chat/types/message.ts +3 -0
- package/src/tools/Chat/utils/index.ts +4 -0
- package/src/tools/CodeEditor/README.md +4 -6
- package/src/tools/CodeEditor/components/DiffEditor.tsx +48 -13
- package/src/tools/CodeEditor/components/Editor.tsx +96 -44
- package/src/tools/CodeEditor/context/EditorProvider.tsx +34 -17
- package/src/tools/CodeEditor/hooks/useEditorTheme.ts +92 -99
- package/src/tools/CodeEditor/hooks/useMonaco.ts +37 -22
- package/src/tools/CodeEditor/lazy.tsx +6 -0
- package/src/tools/CodeEditor/lib/index.ts +1 -1
- package/src/tools/CodeEditor/lib/themes.ts +3 -39
- package/src/tools/CronScheduler/CronScheduler.client.tsx +230 -61
- package/src/tools/CronScheduler/components/CustomInput.tsx +21 -4
- package/src/tools/CronScheduler/components/DayChips.tsx +13 -11
- package/src/tools/CronScheduler/components/MonthDayGrid.tsx +4 -4
- package/src/tools/CronScheduler/components/SchedulePreview.tsx +7 -3
- package/src/tools/CronScheduler/components/TimeSelector.tsx +1 -1
- package/src/tools/CronScheduler/index.tsx +1 -1
- package/src/tools/CronScheduler/types/index.ts +8 -3
- package/src/tools/CronScheduler/utils/cron-humanize.ts +61 -16
- package/src/tools/CronScheduler/utils/cron-parser.ts +13 -4
- package/src/tools/FileIcon/FileIcon.tsx +24 -39
- package/src/tools/FileIcon/get-file-icon.ts +73 -0
- package/src/tools/FileIcon/icons/icon-data.ts +399 -0
- package/src/tools/FileIcon/index.ts +4 -0
- package/src/tools/FileIcon/loader.ts +17 -35
- package/src/tools/FileIcon/specialFolders.ts +18 -0
- package/src/tools/Gallery/components/lightbox/GalleryLightbox.tsx +112 -35
- package/src/tools/Gallery/components/media/GalleryVideo.tsx +21 -2
- package/src/tools/Gallery/components/preview/GalleryCarousel.tsx +11 -1
- package/src/tools/Gallery/hooks/usePreloadImages.ts +54 -7
- package/src/tools/ImageViewer/components/ImageInfo.tsx +12 -1
- package/src/tools/ImageViewer/components/ImageToolbar.tsx +51 -43
- package/src/tools/ImageViewer/components/ImageViewer.tsx +106 -26
- package/src/tools/ImageViewer/hooks/useImageLoading.ts +13 -0
- package/src/tools/ImageViewer/utils/constants.ts +3 -0
- package/src/tools/ImageViewer/utils/index.ts +1 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +4 -1
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +5 -3
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +7 -4
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +3 -1
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +23 -3
- package/src/tools/JsonForm/widgets/ColorWidget.tsx +20 -12
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +14 -9
- package/src/tools/JsonForm/widgets/RadioWidget.tsx +78 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +1 -0
- package/src/tools/JsonForm/widgets/SliderWidget.tsx +7 -4
- package/src/tools/JsonForm/widgets/TextWidget.tsx +41 -17
- package/src/tools/JsonForm/widgets/index.ts +1 -0
- package/src/tools/JsonTree/components/JsonContent.tsx +115 -40
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +177 -72
- package/src/tools/LottiePlayer/index.tsx +14 -4
- package/src/tools/LottiePlayer/lazy.tsx +11 -3
- package/src/tools/LottiePlayer/types.ts +31 -1
- package/src/tools/LottiePlayer/useLottie.ts +32 -9
- package/src/tools/LottiePlayer/usePrefersReducedMotion.ts +46 -0
- package/src/tools/Map/components/LayerSwitcher.tsx +54 -21
- package/src/tools/Map/components/MapCluster.tsx +28 -21
- package/src/tools/Map/components/MapContainer.tsx +11 -4
- package/src/tools/Map/components/MapLegend.tsx +46 -15
- package/src/tools/Map/components/MapMarker.tsx +31 -2
- package/src/tools/Map/hooks/useMapEvents.ts +64 -105
- package/src/tools/MarkdownEditor/MarkdownEditor.tsx +61 -6
- package/src/tools/MarkdownEditor/MentionList.tsx +37 -4
- package/src/tools/MarkdownEditor/createMentionSuggestion.ts +11 -0
- package/src/tools/MarkdownEditor/lazy.tsx +32 -7
- package/src/tools/MarkdownEditor/styles.css +13 -0
- package/src/tools/MarkdownMessage/CodeBlock.tsx +40 -17
- package/src/tools/MarkdownMessage/MarkdownMessage.tsx +26 -6
- package/src/tools/MarkdownMessage/components.tsx +22 -9
- package/src/tools/MarkdownMessage/types.ts +24 -1
- package/src/tools/Mermaid/Mermaid.client.tsx +27 -5
- package/src/tools/Mermaid/components/MermaidErrorPanel.tsx +31 -0
- package/src/tools/Mermaid/components/MermaidFullscreenModal.tsx +14 -17
- package/src/tools/Mermaid/hooks/useMermaidRenderer.ts +264 -168
- package/src/tools/Mermaid/hooks/useMermaidValidation.ts +76 -10
- package/src/tools/Mermaid/index.tsx +6 -0
- package/src/tools/Mermaid/utils/mermaid-helpers.ts +141 -18
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +11 -1
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +49 -20
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +7 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +7 -4
- package/src/tools/OpenapiViewer/constants.ts +3 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +73 -11
- package/src/tools/OpenapiViewer/utils/schemaExport.ts +26 -6
- package/src/tools/PrettyCode/PrettyCode.client.tsx +23 -16
- package/src/tools/PrettyCode/lazy.tsx +1 -1
- package/src/tools/SpeechRecognition/README.md +1 -1
- package/src/tools/SpeechRecognition/__tests__/language.test.ts +9 -3
- package/src/tools/SpeechRecognition/components/RecordingPulse.tsx +59 -0
- package/src/tools/SpeechRecognition/components/index.ts +2 -0
- package/src/tools/SpeechRecognition/core/engine/external.ts +24 -7
- package/src/tools/SpeechRecognition/core/language.ts +23 -6
- package/src/tools/SpeechRecognition/hooks/usePushToTalk.ts +36 -5
- package/src/tools/SpeechRecognition/hooks/useSpeechRecognition.ts +18 -11
- package/src/tools/SpeechRecognition/widgets/VoiceComposerSlot.tsx +94 -26
- package/src/tools/SpeechRecognition/widgets/index.ts +1 -1
- package/src/tools/Tree/README.md +4 -8
- package/src/tools/Tree/TreeRoot.tsx +22 -10
- package/src/tools/Tree/components/TreeContent.tsx +24 -4
- package/src/tools/Tree/components/TreeLabel.tsx +8 -2
- package/src/tools/Tree/components/TreeRow.tsx +16 -6
- package/src/tools/Tree/data/flatten.ts +10 -4
- package/src/tools/Tree/types.ts +4 -0
- package/src/tools/Uploader/components/UploadAddButton.tsx +29 -6
- package/src/tools/Uploader/components/UploadDropzone.tsx +63 -7
- package/src/tools/Uploader/components/UploadPageDropOverlay.tsx +19 -5
- package/src/tools/Uploader/components/UploadPreviewItem.tsx +47 -17
- package/src/tools/Uploader/components/UploadPreviewList.tsx +24 -12
- package/src/tools/Uploader/utils/formatters.ts +8 -3
- package/src/tools/VideoPlayer/README.md +87 -230
- package/src/tools/VideoPlayer/VideoPlayer.tsx +82 -0
- package/src/tools/VideoPlayer/canvas/canvas-dispatcher.tsx +34 -0
- package/src/tools/VideoPlayer/canvas/hls-canvas.tsx +39 -0
- package/src/tools/VideoPlayer/canvas/iframe-canvas.tsx +33 -0
- package/src/tools/VideoPlayer/canvas/index.ts +12 -0
- package/src/tools/VideoPlayer/canvas/jsx-augmentation.ts +47 -0
- package/src/tools/VideoPlayer/canvas/native-canvas.tsx +38 -0
- package/src/tools/VideoPlayer/canvas/vimeo-canvas.tsx +40 -0
- package/src/tools/VideoPlayer/canvas/youtube-canvas.tsx +78 -0
- package/src/tools/VideoPlayer/index.ts +51 -65
- package/src/tools/VideoPlayer/lazy.tsx +11 -54
- package/src/tools/VideoPlayer/parts/controls-bar.tsx +35 -0
- package/src/tools/VideoPlayer/parts/fullscreen.tsx +19 -0
- package/src/tools/VideoPlayer/parts/index.ts +15 -0
- package/src/tools/VideoPlayer/parts/pip.tsx +19 -0
- package/src/tools/VideoPlayer/parts/play-button.tsx +19 -0
- package/src/tools/VideoPlayer/parts/playback-rate.tsx +31 -0
- package/src/tools/VideoPlayer/parts/poster.tsx +3 -0
- package/src/tools/VideoPlayer/parts/seek-bar.tsx +26 -0
- package/src/tools/VideoPlayer/parts/volume.tsx +32 -0
- package/src/tools/VideoPlayer/styles/video-player.css +141 -0
- package/src/tools/VideoPlayer/types.ts +82 -0
- package/src/tools/VideoPlayer/utils/parse-embed-url.ts +70 -0
- package/src/tools/VideoPlayer/utils/vimeo-id.ts +24 -0
- package/src/tools/VideoPlayer/utils/youtube-id.ts +64 -0
- package/src/tools/index.ts +37 -29
- package/src/tools/Chat/components/AudioToggle.tsx +0 -78
- package/src/tools/Chat/components/ChatRoot.tsx +0 -305
- package/src/tools/Chat/components/Composer.tsx +0 -216
- package/src/tools/Chat/hooks/useChatScroll.ts +0 -145
- package/src/tools/Chat/types.ts +0 -9
- package/src/tools/JsonTree/components/JsonToolbar.tsx +0 -95
- package/src/tools/JsonTree/hooks/useElementCorner.ts +0 -84
- package/src/tools/JsonTree/hooks/useNavbarHeight.ts +0 -83
- package/src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts +0 -121
- package/src/tools/Tour/README.md +0 -373
- package/src/tools/Tour/components/Tour.tsx +0 -12
- package/src/tools/Tour/components/TourContent.tsx +0 -171
- package/src/tools/Tour/components/TourNavigation.tsx +0 -77
- package/src/tools/Tour/components/TourProgress.tsx +0 -88
- package/src/tools/Tour/components/TourSpotlight.tsx +0 -199
- package/src/tools/Tour/components/index.ts +0 -5
- package/src/tools/Tour/context/TourContext.ts +0 -19
- package/src/tools/Tour/context/TourProvider.tsx +0 -292
- package/src/tools/Tour/context/index.ts +0 -2
- package/src/tools/Tour/hooks/index.ts +0 -3
- package/src/tools/Tour/hooks/useKeyboardNavigation.ts +0 -59
- package/src/tools/Tour/hooks/useStepTarget.ts +0 -121
- package/src/tools/Tour/hooks/useTour.ts +0 -42
- package/src/tools/Tour/index.ts +0 -38
- package/src/tools/Tour/types/index.ts +0 -224
- package/src/tools/Tour/utils/dom.ts +0 -98
- package/src/tools/Tour/utils/index.ts +0 -3
- package/src/tools/Tour/utils/logger.ts +0 -3
- package/src/tools/Tour/utils/scrollIntoView.ts +0 -24
- package/src/tools/VideoPlayer/components/VideoControls.tsx +0 -138
- package/src/tools/VideoPlayer/components/VideoErrorFallback.tsx +0 -172
- package/src/tools/VideoPlayer/components/VideoPlayer.tsx +0 -201
- package/src/tools/VideoPlayer/components/index.ts +0 -14
- package/src/tools/VideoPlayer/context/VideoPlayerContext.tsx +0 -52
- package/src/tools/VideoPlayer/context/index.ts +0 -8
- package/src/tools/VideoPlayer/hooks/index.ts +0 -12
- package/src/tools/VideoPlayer/hooks/useVideoPlayerSettings.ts +0 -71
- package/src/tools/VideoPlayer/hooks/useVideoPositionCache.ts +0 -117
- package/src/tools/VideoPlayer/providers/NativeProvider.tsx +0 -284
- package/src/tools/VideoPlayer/providers/StreamProvider.tsx +0 -505
- package/src/tools/VideoPlayer/providers/VidstackProvider.tsx +0 -397
- package/src/tools/VideoPlayer/providers/index.ts +0 -8
- package/src/tools/VideoPlayer/types/index.ts +0 -38
- package/src/tools/VideoPlayer/types/player.ts +0 -116
- package/src/tools/VideoPlayer/types/provider.ts +0 -93
- package/src/tools/VideoPlayer/types/sources.ts +0 -97
- package/src/tools/VideoPlayer/utils/debug.ts +0 -14
- package/src/tools/VideoPlayer/utils/fileSource.ts +0 -78
- package/src/tools/VideoPlayer/utils/index.ts +0 -12
- package/src/tools/VideoPlayer/utils/resolvers.ts +0 -75
- /package/src/tools/Chat/{config.ts → constants.ts} +0 -0
- /package/src/tools/Chat/launcher/{ChatHeaderAudioToggle.tsx → header/ChatHeaderAudioToggle.tsx} +0 -0
- /package/src/tools/Chat/{components → messages}/Attachments.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/JumpToLatest.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/MessageActions.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/Sources.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/StreamingIndicator.tsx +0 -0
- /package/src/tools/Chat/{components → messages}/ToolCalls.tsx +0 -0
- /package/src/tools/Chat/{components → shell}/ErrorBanner.tsx +0 -0
|
@@ -11,6 +11,7 @@ import Lottie from 'react-lottie-player';
|
|
|
11
11
|
|
|
12
12
|
import { LottiePlayerProps } from './types';
|
|
13
13
|
import { useLottie } from './useLottie';
|
|
14
|
+
import { usePrefersReducedMotion } from './usePrefersReducedMotion';
|
|
14
15
|
|
|
15
16
|
// Size presets mapping
|
|
16
17
|
const SIZE_PRESETS = {
|
|
@@ -22,42 +23,33 @@ const SIZE_PRESETS = {
|
|
|
22
23
|
full: { width: '100%', height: '100%' },
|
|
23
24
|
} as const;
|
|
24
25
|
|
|
26
|
+
const SPEED_STEPS = [0.5, 1, 1.5, 2] as const;
|
|
27
|
+
|
|
25
28
|
/**
|
|
26
29
|
* LottiePlayer component for displaying Lottie animations
|
|
27
30
|
*
|
|
28
31
|
* Features:
|
|
29
32
|
* - Loads animations from URLs or objects
|
|
30
33
|
* - Size presets or custom dimensions
|
|
31
|
-
* -
|
|
34
|
+
* - Interactive playback controls (play/pause, loop, speed)
|
|
35
|
+
* - Segment playback and hover-to-play
|
|
32
36
|
* - Loading and error states
|
|
37
|
+
* - Respects `prefers-reduced-motion`
|
|
33
38
|
* - Event callbacks
|
|
34
39
|
*
|
|
35
40
|
* Usage:
|
|
36
41
|
* ```tsx
|
|
37
42
|
* // From URL with size preset
|
|
38
|
-
* <LottiePlayer
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* loop
|
|
43
|
-
* />
|
|
43
|
+
* <LottiePlayer src="https://example.com/animation.json" size="md" autoplay loop />
|
|
44
|
+
*
|
|
45
|
+
* // Interactive controls
|
|
46
|
+
* <LottiePlayer src={animationData} controls />
|
|
44
47
|
*
|
|
45
|
-
* //
|
|
46
|
-
* <LottiePlayer
|
|
47
|
-
* src={animationData}
|
|
48
|
-
* width={300}
|
|
49
|
-
* height={300}
|
|
50
|
-
* speed={1.5}
|
|
51
|
-
* controls
|
|
52
|
-
* />
|
|
48
|
+
* // Play a frame range only
|
|
49
|
+
* <LottiePlayer src={animationData} segment={[30, 90]} />
|
|
53
50
|
*
|
|
54
|
-
* //
|
|
55
|
-
* <LottiePlayer
|
|
56
|
-
* src="https://example.com/animation.json"
|
|
57
|
-
* onLoad={() => console.log('Animation loaded')}
|
|
58
|
-
* onComplete={() => console.log('Animation completed')}
|
|
59
|
-
* onError={(err) => console.error('Error:', err)}
|
|
60
|
-
* />
|
|
51
|
+
* // Play on hover
|
|
52
|
+
* <LottiePlayer src={animationData} hoverToPlay autoplay={false} />
|
|
61
53
|
* ```
|
|
62
54
|
*/
|
|
63
55
|
export function LottiePlayer({
|
|
@@ -70,8 +62,12 @@ export function LottiePlayer({
|
|
|
70
62
|
speed = 1,
|
|
71
63
|
direction = 1,
|
|
72
64
|
controls = false,
|
|
65
|
+
segment,
|
|
66
|
+
hoverToPlay = false,
|
|
73
67
|
background,
|
|
74
68
|
className,
|
|
69
|
+
ariaLabel,
|
|
70
|
+
respectReducedMotion = true,
|
|
75
71
|
showLoading = true,
|
|
76
72
|
onComplete,
|
|
77
73
|
onLoad,
|
|
@@ -79,18 +75,38 @@ export function LottiePlayer({
|
|
|
79
75
|
}: LottiePlayerProps) {
|
|
80
76
|
// Load animation data using our custom hook
|
|
81
77
|
const { animationData, isLoading, error, retry } = useLottie({ src });
|
|
78
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
79
|
+
const reduceMotion = respectReducedMotion && prefersReducedMotion;
|
|
80
|
+
|
|
81
|
+
// Interactive playback state (only used when `controls` is enabled).
|
|
82
|
+
const [isPlaying, setIsPlaying] = React.useState(autoplay);
|
|
83
|
+
const [loopEnabled, setLoopEnabled] = React.useState(Boolean(loop));
|
|
84
|
+
const [currentSpeed, setCurrentSpeed] = React.useState<number>(speed);
|
|
85
|
+
const [hovered, setHovered] = React.useState(false);
|
|
82
86
|
|
|
83
|
-
//
|
|
87
|
+
// Keep internal state in sync if controlling props change.
|
|
88
|
+
React.useEffect(() => setIsPlaying(autoplay), [autoplay]);
|
|
89
|
+
React.useEffect(() => setLoopEnabled(Boolean(loop)), [loop]);
|
|
90
|
+
React.useEffect(() => setCurrentSpeed(speed), [speed]);
|
|
91
|
+
|
|
92
|
+
// Notify parent about load state once per loaded animation.
|
|
93
|
+
const notifiedLoadRef = React.useRef<object | null>(null);
|
|
84
94
|
React.useEffect(() => {
|
|
85
|
-
if (animationData &&
|
|
86
|
-
|
|
95
|
+
if (animationData && notifiedLoadRef.current !== animationData) {
|
|
96
|
+
notifiedLoadRef.current = animationData;
|
|
97
|
+
onLoad?.();
|
|
87
98
|
}
|
|
88
99
|
}, [animationData, onLoad]);
|
|
89
100
|
|
|
90
|
-
// Notify parent about errors
|
|
101
|
+
// Notify parent about errors once per error instance.
|
|
102
|
+
const notifiedErrorRef = React.useRef<Error | null>(null);
|
|
91
103
|
React.useEffect(() => {
|
|
92
|
-
if (error &&
|
|
93
|
-
|
|
104
|
+
if (error && notifiedErrorRef.current !== error) {
|
|
105
|
+
notifiedErrorRef.current = error;
|
|
106
|
+
onError?.(error);
|
|
107
|
+
}
|
|
108
|
+
if (!error) {
|
|
109
|
+
notifiedErrorRef.current = null;
|
|
94
110
|
}
|
|
95
111
|
}, [error, onError]);
|
|
96
112
|
|
|
@@ -103,32 +119,71 @@ export function LottiePlayer({
|
|
|
103
119
|
height: height ?? SIZE_PRESETS[size].height,
|
|
104
120
|
};
|
|
105
121
|
}
|
|
106
|
-
|
|
107
|
-
// Use size preset
|
|
108
122
|
return SIZE_PRESETS[size];
|
|
109
123
|
}, [size, width, height]);
|
|
110
124
|
|
|
111
125
|
// Handle complete event
|
|
112
126
|
const handleComplete = React.useCallback(() => {
|
|
113
|
-
|
|
114
|
-
|
|
127
|
+
// When not looping, reflect the finished state in the controls.
|
|
128
|
+
if (!loopEnabled) {
|
|
129
|
+
setIsPlaying(false);
|
|
115
130
|
}
|
|
116
|
-
|
|
131
|
+
onComplete?.();
|
|
132
|
+
}, [loopEnabled, onComplete]);
|
|
133
|
+
|
|
134
|
+
const togglePlay = React.useCallback(() => {
|
|
135
|
+
setIsPlaying((prev) => !prev);
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
138
|
+
const cycleSpeed = React.useCallback(() => {
|
|
139
|
+
setCurrentSpeed((prev) => {
|
|
140
|
+
const idx = SPEED_STEPS.indexOf(prev as (typeof SPEED_STEPS)[number]);
|
|
141
|
+
return SPEED_STEPS[(idx + 1) % SPEED_STEPS.length];
|
|
142
|
+
});
|
|
143
|
+
}, []);
|
|
144
|
+
|
|
145
|
+
// Resolve the effective play / loop / speed values.
|
|
146
|
+
// Reduced motion forces a paused, non-looping static frame.
|
|
147
|
+
const effectivePlaying = reduceMotion
|
|
148
|
+
? false
|
|
149
|
+
: hoverToPlay
|
|
150
|
+
? hovered
|
|
151
|
+
: controls
|
|
152
|
+
? isPlaying
|
|
153
|
+
: autoplay;
|
|
154
|
+
const effectiveLoop = reduceMotion ? false : controls ? loopEnabled : loop;
|
|
155
|
+
const effectiveSpeed = controls ? currentSpeed : speed;
|
|
156
|
+
|
|
157
|
+
// Segment playback: react-lottie-player expects [start, end].
|
|
158
|
+
const segments = React.useMemo(
|
|
159
|
+
() => (segment ? ([segment[0], segment[1]] as [number, number]) : undefined),
|
|
160
|
+
[segment]
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const containerStyle = React.useMemo<React.CSSProperties>(
|
|
164
|
+
() => ({
|
|
165
|
+
width: dimensions.width,
|
|
166
|
+
height: dimensions.height,
|
|
167
|
+
background: background || 'transparent',
|
|
168
|
+
}),
|
|
169
|
+
[dimensions.width, dimensions.height, background]
|
|
170
|
+
);
|
|
117
171
|
|
|
118
172
|
// Loading state
|
|
119
173
|
if (isLoading && showLoading) {
|
|
120
174
|
return (
|
|
121
175
|
<div
|
|
122
176
|
className={`flex items-center justify-center ${className || ''}`}
|
|
123
|
-
style={
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
background: background || 'transparent',
|
|
127
|
-
}}
|
|
177
|
+
style={containerStyle}
|
|
178
|
+
role="status"
|
|
179
|
+
aria-live="polite"
|
|
128
180
|
>
|
|
129
181
|
<div className="flex flex-col items-center gap-2">
|
|
130
|
-
<div
|
|
131
|
-
|
|
182
|
+
<div
|
|
183
|
+
className="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-foreground motion-reduce:animate-[spin_1.5s_linear_infinite]"
|
|
184
|
+
aria-hidden="true"
|
|
185
|
+
/>
|
|
186
|
+
<span className="text-sm text-muted-foreground">Loading animation...</span>
|
|
132
187
|
</div>
|
|
133
188
|
</div>
|
|
134
189
|
);
|
|
@@ -139,18 +194,16 @@ export function LottiePlayer({
|
|
|
139
194
|
return (
|
|
140
195
|
<div
|
|
141
196
|
className={`flex items-center justify-center ${className || ''}`}
|
|
142
|
-
style={
|
|
143
|
-
|
|
144
|
-
height: dimensions.height,
|
|
145
|
-
background: background || 'transparent',
|
|
146
|
-
}}
|
|
197
|
+
style={containerStyle}
|
|
198
|
+
role="alert"
|
|
147
199
|
>
|
|
148
200
|
<div className="flex flex-col items-center gap-2 p-4 text-center">
|
|
149
201
|
<svg
|
|
150
|
-
className="h-8 w-8 text-
|
|
202
|
+
className="h-8 w-8 text-destructive"
|
|
151
203
|
fill="none"
|
|
152
204
|
stroke="currentColor"
|
|
153
205
|
viewBox="0 0 24 24"
|
|
206
|
+
aria-hidden="true"
|
|
154
207
|
>
|
|
155
208
|
<path
|
|
156
209
|
strokeLinecap="round"
|
|
@@ -159,10 +212,11 @@ export function LottiePlayer({
|
|
|
159
212
|
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
160
213
|
/>
|
|
161
214
|
</svg>
|
|
162
|
-
<div className="text-sm text-
|
|
215
|
+
<div className="text-sm text-destructive">{error.message}</div>
|
|
163
216
|
<button
|
|
217
|
+
type="button"
|
|
164
218
|
onClick={retry}
|
|
165
|
-
className="rounded bg-
|
|
219
|
+
className="rounded bg-destructive/10 px-3 py-1 text-sm text-destructive transition-colors hover:bg-destructive/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
166
220
|
>
|
|
167
221
|
Retry
|
|
168
222
|
</button>
|
|
@@ -180,32 +234,83 @@ export function LottiePlayer({
|
|
|
180
234
|
return (
|
|
181
235
|
<div
|
|
182
236
|
className={className}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}}
|
|
237
|
+
role="img"
|
|
238
|
+
aria-label={ariaLabel ?? 'Animation'}
|
|
239
|
+
onMouseEnter={hoverToPlay ? () => setHovered(true) : undefined}
|
|
240
|
+
onMouseLeave={hoverToPlay ? () => setHovered(false) : undefined}
|
|
188
241
|
>
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
242
|
+
<div style={containerStyle}>
|
|
243
|
+
<Lottie
|
|
244
|
+
animationData={animationData}
|
|
245
|
+
play={effectivePlaying}
|
|
246
|
+
loop={effectiveLoop}
|
|
247
|
+
speed={effectiveSpeed}
|
|
248
|
+
direction={direction}
|
|
249
|
+
segments={segments}
|
|
250
|
+
goTo={reduceMotion ? (segment ? segment[0] : 0) : undefined}
|
|
251
|
+
style={{ width: '100%', height: '100%' }}
|
|
252
|
+
onComplete={handleComplete}
|
|
253
|
+
rendererSettings={{
|
|
254
|
+
preserveAspectRatio: 'xMidYMid meet',
|
|
255
|
+
}}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
204
258
|
{controls && (
|
|
205
|
-
<div className="mt-2 flex items-center justify-center gap-
|
|
206
|
-
<
|
|
207
|
-
|
|
208
|
-
|
|
259
|
+
<div className="mt-2 flex items-center justify-center gap-1">
|
|
260
|
+
<button
|
|
261
|
+
type="button"
|
|
262
|
+
onClick={togglePlay}
|
|
263
|
+
disabled={reduceMotion}
|
|
264
|
+
aria-label={effectivePlaying ? 'Pause animation' : 'Play animation'}
|
|
265
|
+
aria-pressed={effectivePlaying}
|
|
266
|
+
className="inline-flex h-7 w-7 items-center justify-center rounded text-foreground transition-colors hover:bg-muted disabled:opacity-40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
267
|
+
>
|
|
268
|
+
{effectivePlaying ? (
|
|
269
|
+
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
270
|
+
<rect x="6" y="5" width="4" height="14" rx="1" />
|
|
271
|
+
<rect x="14" y="5" width="4" height="14" rx="1" />
|
|
272
|
+
</svg>
|
|
273
|
+
) : (
|
|
274
|
+
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
275
|
+
<path d="M8 5v14l11-7z" />
|
|
276
|
+
</svg>
|
|
277
|
+
)}
|
|
278
|
+
</button>
|
|
279
|
+
<button
|
|
280
|
+
type="button"
|
|
281
|
+
onClick={() => setLoopEnabled((prev) => !prev)}
|
|
282
|
+
aria-label={loopEnabled ? 'Disable loop' : 'Enable loop'}
|
|
283
|
+
aria-pressed={loopEnabled}
|
|
284
|
+
className={`inline-flex h-7 w-7 items-center justify-center rounded transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
|
285
|
+
loopEnabled ? 'text-foreground' : 'text-muted-foreground'
|
|
286
|
+
}`}
|
|
287
|
+
>
|
|
288
|
+
<svg
|
|
289
|
+
className="h-4 w-4"
|
|
290
|
+
viewBox="0 0 24 24"
|
|
291
|
+
fill="none"
|
|
292
|
+
stroke="currentColor"
|
|
293
|
+
strokeWidth={2}
|
|
294
|
+
aria-hidden="true"
|
|
295
|
+
>
|
|
296
|
+
<path
|
|
297
|
+
strokeLinecap="round"
|
|
298
|
+
strokeLinejoin="round"
|
|
299
|
+
d="M17 1l4 4-4 4M3 11V9a4 4 0 014-4h14M7 23l-4-4 4-4M21 13v2a4 4 0 01-4 4H3"
|
|
300
|
+
/>
|
|
301
|
+
</svg>
|
|
302
|
+
</button>
|
|
303
|
+
<button
|
|
304
|
+
type="button"
|
|
305
|
+
onClick={cycleSpeed}
|
|
306
|
+
aria-label={`Playback speed ${currentSpeed}x, click to change`}
|
|
307
|
+
className="inline-flex h-7 min-w-9 items-center justify-center rounded px-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
308
|
+
>
|
|
309
|
+
{currentSpeed}x
|
|
310
|
+
</button>
|
|
311
|
+
{reduceMotion && (
|
|
312
|
+
<span className="ml-1 text-xs text-muted-foreground">Reduced motion</span>
|
|
313
|
+
)}
|
|
209
314
|
</div>
|
|
210
315
|
)}
|
|
211
316
|
</div>
|
|
@@ -16,10 +16,13 @@ const LottiePlayerClient = lazy(() =>
|
|
|
16
16
|
|
|
17
17
|
// Loading fallback component
|
|
18
18
|
const LoadingFallback = () => (
|
|
19
|
-
<div className="flex items-center justify-center p-8">
|
|
19
|
+
<div className="flex items-center justify-center p-8" role="status" aria-live="polite">
|
|
20
20
|
<div className="flex flex-col items-center gap-2">
|
|
21
|
-
<div
|
|
22
|
-
|
|
21
|
+
<div
|
|
22
|
+
className="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-foreground motion-reduce:animate-[spin_1.5s_linear_infinite]"
|
|
23
|
+
aria-hidden="true"
|
|
24
|
+
/>
|
|
25
|
+
<span className="text-sm text-muted-foreground">Loading player...</span>
|
|
23
26
|
</div>
|
|
24
27
|
</div>
|
|
25
28
|
);
|
|
@@ -51,6 +54,13 @@ export function LottiePlayer(props: LottiePlayerProps) {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
// Re-export types for convenience
|
|
54
|
-
export type {
|
|
57
|
+
export type {
|
|
58
|
+
LottiePlayerProps,
|
|
59
|
+
LottieSize,
|
|
60
|
+
LottieSpeed,
|
|
61
|
+
LottieDirection,
|
|
62
|
+
LottieSegment,
|
|
63
|
+
} from './types';
|
|
55
64
|
export { useLottie } from './useLottie';
|
|
56
65
|
export type { UseLottieOptions, UseLottieReturn } from './useLottie';
|
|
66
|
+
export { usePrefersReducedMotion } from './usePrefersReducedMotion';
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* import { LottiePlayer } from '@djangocfg/ui-tools/lottie'
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { createLazyComponent
|
|
13
|
+
import { createLazyComponent } from '../../components';
|
|
14
14
|
import type { LottiePlayerProps } from './types';
|
|
15
15
|
|
|
16
16
|
// ============================================================================
|
|
@@ -22,6 +22,7 @@ export type {
|
|
|
22
22
|
LottieSize,
|
|
23
23
|
LottieSpeed,
|
|
24
24
|
LottieDirection,
|
|
25
|
+
LottieSegment,
|
|
25
26
|
} from './types';
|
|
26
27
|
|
|
27
28
|
// ============================================================================
|
|
@@ -30,9 +31,16 @@ export type {
|
|
|
30
31
|
|
|
31
32
|
function LottieLoadingFallback() {
|
|
32
33
|
return (
|
|
33
|
-
<div
|
|
34
|
+
<div
|
|
35
|
+
className="flex items-center justify-center p-8"
|
|
36
|
+
role="status"
|
|
37
|
+
aria-live="polite"
|
|
38
|
+
>
|
|
34
39
|
<div className="flex flex-col items-center gap-2">
|
|
35
|
-
<div
|
|
40
|
+
<div
|
|
41
|
+
className="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-primary motion-reduce:animate-[spin_1.5s_linear_infinite]"
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
/>
|
|
36
44
|
<span className="text-sm text-muted-foreground">Loading animation...</span>
|
|
37
45
|
</div>
|
|
38
46
|
</div>
|
|
@@ -10,6 +10,11 @@ export type LottieSpeed = 0.5 | 1 | 1.5 | 2;
|
|
|
10
10
|
|
|
11
11
|
export type LottieDirection = 1 | -1;
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Inclusive [start, end] frame range for segment playback.
|
|
15
|
+
*/
|
|
16
|
+
export type LottieSegment = [number, number];
|
|
17
|
+
|
|
13
18
|
export interface LottiePlayerProps {
|
|
14
19
|
/**
|
|
15
20
|
* Animation data (JSON object) or URL to load from
|
|
@@ -57,11 +62,23 @@ export interface LottiePlayerProps {
|
|
|
57
62
|
direction?: LottieDirection;
|
|
58
63
|
|
|
59
64
|
/**
|
|
60
|
-
* Show playback controls
|
|
65
|
+
* Show interactive playback controls (play/pause, loop, speed)
|
|
61
66
|
* @default false
|
|
62
67
|
*/
|
|
63
68
|
controls?: boolean;
|
|
64
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Play only a frame range, e.g. [30, 90]. Overrides full-clip playback.
|
|
72
|
+
*/
|
|
73
|
+
segment?: LottieSegment;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Play the animation only while the pointer is over it. When the
|
|
77
|
+
* pointer leaves, playback pauses. Ignored when `controls` drive playback.
|
|
78
|
+
* @default false
|
|
79
|
+
*/
|
|
80
|
+
hoverToPlay?: boolean;
|
|
81
|
+
|
|
65
82
|
/**
|
|
66
83
|
* Background color
|
|
67
84
|
*/
|
|
@@ -72,6 +89,19 @@ export interface LottiePlayerProps {
|
|
|
72
89
|
*/
|
|
73
90
|
className?: string;
|
|
74
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Accessible label describing the animation for screen readers.
|
|
94
|
+
*/
|
|
95
|
+
ariaLabel?: string;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Respect the user's `prefers-reduced-motion` setting. When the user
|
|
99
|
+
* prefers reduced motion the animation renders a static first frame
|
|
100
|
+
* instead of playing.
|
|
101
|
+
* @default true
|
|
102
|
+
*/
|
|
103
|
+
respectReducedMotion?: boolean;
|
|
104
|
+
|
|
75
105
|
/**
|
|
76
106
|
* Show loading state
|
|
77
107
|
* @default true
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import { useEffect, useRef, useState } from 'react';
|
|
10
10
|
|
|
11
|
-
import { LottieAnimationData } from './types';
|
|
12
|
-
|
|
13
11
|
export interface UseLottieOptions {
|
|
14
12
|
/**
|
|
15
13
|
* Animation data (JSON object) or URL to load from
|
|
@@ -54,6 +52,7 @@ const animationCache = new Map<string, object>();
|
|
|
54
52
|
* Features:
|
|
55
53
|
* - Loads animations from URLs or accepts animation objects directly
|
|
56
54
|
* - Caching support to prevent re-fetching the same animation
|
|
55
|
+
* - Aborts in-flight requests on unmount / src change
|
|
57
56
|
* - Error handling with retry capability
|
|
58
57
|
* - Loading states
|
|
59
58
|
*
|
|
@@ -89,9 +88,12 @@ export function useLottie(options: UseLottieOptions): UseLottieReturn {
|
|
|
89
88
|
}, []);
|
|
90
89
|
|
|
91
90
|
useEffect(() => {
|
|
92
|
-
// If src is already an object, use it directly
|
|
91
|
+
// If src is already an object, use it directly.
|
|
92
|
+
// Only update state when the reference actually changes so that
|
|
93
|
+
// callers passing an inline object literal do not trigger an
|
|
94
|
+
// infinite render loop.
|
|
93
95
|
if (typeof src === 'object' && src !== null) {
|
|
94
|
-
setAnimationData(src);
|
|
96
|
+
setAnimationData((prev) => (prev === src ? prev : src));
|
|
95
97
|
setIsLoading(false);
|
|
96
98
|
setError(null);
|
|
97
99
|
return;
|
|
@@ -99,6 +101,8 @@ export function useLottie(options: UseLottieOptions): UseLottieReturn {
|
|
|
99
101
|
|
|
100
102
|
// If src is a string (URL), fetch it
|
|
101
103
|
if (typeof src === 'string') {
|
|
104
|
+
const abortController = new AbortController();
|
|
105
|
+
|
|
102
106
|
const loadAnimation = async () => {
|
|
103
107
|
// Check cache first
|
|
104
108
|
if (cache && animationCache.has(src)) {
|
|
@@ -117,29 +121,44 @@ export function useLottie(options: UseLottieOptions): UseLottieReturn {
|
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
try {
|
|
120
|
-
const response = await fetch(src);
|
|
124
|
+
const response = await fetch(src, { signal: abortController.signal });
|
|
121
125
|
|
|
122
126
|
if (!response.ok) {
|
|
123
127
|
throw new Error(`Failed to load animation: ${response.status} ${response.statusText}`);
|
|
124
128
|
}
|
|
125
129
|
|
|
126
|
-
|
|
130
|
+
let data: unknown;
|
|
131
|
+
try {
|
|
132
|
+
data = await response.json();
|
|
133
|
+
} catch {
|
|
134
|
+
throw new Error('Animation file is not valid JSON');
|
|
135
|
+
}
|
|
127
136
|
|
|
128
137
|
// Validate that it's a valid Lottie animation
|
|
129
|
-
if (
|
|
138
|
+
if (
|
|
139
|
+
!data ||
|
|
140
|
+
typeof data !== 'object' ||
|
|
141
|
+
!('v' in data) ||
|
|
142
|
+
!('layers' in data) ||
|
|
143
|
+
!Array.isArray((data as { layers: unknown }).layers)
|
|
144
|
+
) {
|
|
130
145
|
throw new Error('Invalid Lottie animation data');
|
|
131
146
|
}
|
|
132
147
|
|
|
133
148
|
// Cache the loaded animation
|
|
134
149
|
if (cache) {
|
|
135
|
-
animationCache.set(src, data);
|
|
150
|
+
animationCache.set(src, data as object);
|
|
136
151
|
}
|
|
137
152
|
|
|
138
153
|
if (isMountedRef.current) {
|
|
139
|
-
setAnimationData(data);
|
|
154
|
+
setAnimationData(data as object);
|
|
140
155
|
setIsLoading(false);
|
|
141
156
|
}
|
|
142
157
|
} catch (err) {
|
|
158
|
+
// Ignore aborts triggered by unmount / src change.
|
|
159
|
+
if (abortController.signal.aborted) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
143
162
|
if (isMountedRef.current) {
|
|
144
163
|
setError(err instanceof Error ? err : new Error('Failed to load animation'));
|
|
145
164
|
setIsLoading(false);
|
|
@@ -148,6 +167,10 @@ export function useLottie(options: UseLottieOptions): UseLottieReturn {
|
|
|
148
167
|
};
|
|
149
168
|
|
|
150
169
|
loadAnimation();
|
|
170
|
+
|
|
171
|
+
return () => {
|
|
172
|
+
abortController.abort();
|
|
173
|
+
};
|
|
151
174
|
}
|
|
152
175
|
}, [src, cache, retryCount]);
|
|
153
176
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* usePrefersReducedMotion Hook
|
|
3
|
+
*
|
|
4
|
+
* Tracks the `prefers-reduced-motion` media query so animations can be
|
|
5
|
+
* suppressed for users who request reduced motion (a11y).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use client';
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from 'react';
|
|
11
|
+
|
|
12
|
+
const QUERY = '(prefers-reduced-motion: reduce)';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns `true` when the user has requested reduced motion.
|
|
16
|
+
*
|
|
17
|
+
* SSR-safe: returns `false` on the server and during the first client
|
|
18
|
+
* render, then syncs with the actual media query after mount.
|
|
19
|
+
*/
|
|
20
|
+
export function usePrefersReducedMotion(): boolean {
|
|
21
|
+
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (typeof window === 'undefined' || !window.matchMedia) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const mediaQuery = window.matchMedia(QUERY);
|
|
29
|
+
setPrefersReducedMotion(mediaQuery.matches);
|
|
30
|
+
|
|
31
|
+
const handleChange = (event: MediaQueryListEvent) => {
|
|
32
|
+
setPrefersReducedMotion(event.matches);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Safari < 14 only supports the deprecated addListener API.
|
|
36
|
+
if (typeof mediaQuery.addEventListener === 'function') {
|
|
37
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
38
|
+
return () => mediaQuery.removeEventListener('change', handleChange);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
mediaQuery.addListener(handleChange);
|
|
42
|
+
return () => mediaQuery.removeListener(handleChange);
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
return prefersReducedMotion;
|
|
46
|
+
}
|