@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
3
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
4
4
|
import {
|
|
5
5
|
useBatchAddListener,
|
|
6
6
|
useItemProgressListener,
|
|
@@ -28,6 +28,11 @@ export function UploadPreviewList({
|
|
|
28
28
|
const abortItem = useAbortItem();
|
|
29
29
|
const { upload } = useUploady();
|
|
30
30
|
|
|
31
|
+
// Live ref to items so unmount cleanup sees the latest object URLs
|
|
32
|
+
// (a [] effect would only ever capture the initial empty Map).
|
|
33
|
+
const itemsRef = useRef(items);
|
|
34
|
+
itemsRef.current = items;
|
|
35
|
+
|
|
31
36
|
// Batch added - create initial items
|
|
32
37
|
useBatchAddListener((batch) => {
|
|
33
38
|
setItems(prev => {
|
|
@@ -36,8 +41,8 @@ export function UploadPreviewList({
|
|
|
36
41
|
const file = item.file as File;
|
|
37
42
|
let previewUrl: string | undefined;
|
|
38
43
|
|
|
39
|
-
// Create object URL for image preview
|
|
40
|
-
if (file.type.startsWith('image/')) {
|
|
44
|
+
// Create object URL for image/video preview
|
|
45
|
+
if (file.type.startsWith('image/') || file.type.startsWith('video/')) {
|
|
41
46
|
previewUrl = URL.createObjectURL(file);
|
|
42
47
|
}
|
|
43
48
|
|
|
@@ -80,11 +85,18 @@ export function UploadPreviewList({
|
|
|
80
85
|
? buildAssetFromResponse(response, existing.file)
|
|
81
86
|
: undefined;
|
|
82
87
|
|
|
88
|
+
// The server now hosts the file — revoke the local object URL so it
|
|
89
|
+
// can be garbage-collected, and keep only a server-hosted preview URL.
|
|
90
|
+
if (existing.previewUrl) {
|
|
91
|
+
URL.revokeObjectURL(existing.previewUrl);
|
|
92
|
+
}
|
|
93
|
+
|
|
83
94
|
next.set(item.id, {
|
|
84
95
|
...existing,
|
|
85
96
|
status: 'complete',
|
|
86
97
|
progress: 100,
|
|
87
98
|
asset,
|
|
99
|
+
previewUrl: asset?.thumbnailUrl || asset?.url || undefined,
|
|
88
100
|
});
|
|
89
101
|
}
|
|
90
102
|
return next;
|
|
@@ -153,15 +165,15 @@ export function UploadPreviewList({
|
|
|
153
165
|
const handleRetry = useCallback((id: string) => {
|
|
154
166
|
const item = items.get(id);
|
|
155
167
|
if (item && (item.status === 'error' || item.status === 'aborted')) {
|
|
156
|
-
//
|
|
168
|
+
// rpldy.upload() creates a NEW batch item with a fresh id, so the old
|
|
169
|
+
// entry must be dropped (and its object URL revoked) to avoid a stale
|
|
170
|
+
// ghost item and a leaked URL. useBatchAddListener adds the new one.
|
|
171
|
+
if (item.previewUrl) {
|
|
172
|
+
URL.revokeObjectURL(item.previewUrl);
|
|
173
|
+
}
|
|
157
174
|
setItems(prev => {
|
|
158
175
|
const next = new Map(prev);
|
|
159
|
-
next.
|
|
160
|
-
...item,
|
|
161
|
-
status: 'pending',
|
|
162
|
-
progress: 0,
|
|
163
|
-
error: undefined,
|
|
164
|
-
});
|
|
176
|
+
next.delete(id);
|
|
165
177
|
return next;
|
|
166
178
|
});
|
|
167
179
|
|
|
@@ -171,10 +183,10 @@ export function UploadPreviewList({
|
|
|
171
183
|
}
|
|
172
184
|
}, [items, upload, onRetry]);
|
|
173
185
|
|
|
174
|
-
// Cleanup object URLs on unmount
|
|
186
|
+
// Cleanup object URLs on unmount (reads live ref, not a stale closure)
|
|
175
187
|
useEffect(() => {
|
|
176
188
|
return () => {
|
|
177
|
-
|
|
189
|
+
itemsRef.current.forEach(item => {
|
|
178
190
|
if (item.previewUrl) {
|
|
179
191
|
URL.revokeObjectURL(item.previewUrl);
|
|
180
192
|
}
|
|
@@ -27,17 +27,22 @@ export function truncateFilename(filename: string, maxLength = 32): string {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function formatFileSize(bytes: number): string {
|
|
30
|
-
if (bytes
|
|
30
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
|
|
31
31
|
|
|
32
|
-
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
32
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
33
33
|
const k = 1024;
|
|
34
|
-
|
|
34
|
+
// Clamp the unit index so absurdly large values never index out of bounds.
|
|
35
|
+
const i = Math.min(
|
|
36
|
+
Math.floor(Math.log(bytes) / Math.log(k)),
|
|
37
|
+
units.length - 1,
|
|
38
|
+
);
|
|
35
39
|
const size = bytes / Math.pow(k, i);
|
|
36
40
|
|
|
37
41
|
return `${size.toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export function formatDuration(seconds: number): string {
|
|
45
|
+
if (!Number.isFinite(seconds) || seconds < 0) return '0:00';
|
|
41
46
|
const mins = Math.floor(seconds / 60);
|
|
42
47
|
const secs = Math.floor(seconds % 60);
|
|
43
48
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
@@ -1,264 +1,121 @@
|
|
|
1
1
|
# VideoPlayer
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
| Mode | Source Types | Use Case |
|
|
8
|
-
|------|-------------|----------|
|
|
9
|
-
| `vidstack` | YouTube, Vimeo, HLS, DASH | Full-featured player with themes and controls |
|
|
10
|
-
| `native` | URL, data-url | Lightweight HTML5 player |
|
|
11
|
-
| `streaming` | stream, blob | HTTP Range streaming with auth, binary data |
|
|
12
|
-
|
|
13
|
-
Mode is auto-detected from source type, or can be forced via `mode` prop.
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
3
|
+
Composable video player built on [media-chrome](https://media-chrome.org)
|
|
4
|
+
(Mux, MIT, ~12 KB). One `<MediaController>` shell drives **YouTube**,
|
|
5
|
+
**Vimeo**, **HLS**, native **MP4/WebM**, and arbitrary **iframe**
|
|
6
|
+
embeds — the source element swaps, the UI stays the same.
|
|
16
7
|
|
|
17
8
|
```tsx
|
|
18
|
-
import {
|
|
19
|
-
VideoPlayer,
|
|
20
|
-
VideoPlayerProvider,
|
|
21
|
-
VideoErrorFallback,
|
|
22
|
-
resolveFileSource
|
|
23
|
-
} from '@djangocfg/ui-nextjs';
|
|
24
|
-
```
|
|
9
|
+
import { VideoPlayer } from '@djangocfg/ui-tools/video-player';
|
|
25
10
|
|
|
26
|
-
|
|
11
|
+
// Structured source
|
|
12
|
+
<VideoPlayer source={{ type: 'youtube', videoId: 'sGbxmsDFVnE' }} />
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
<VideoPlayer source={{ type: 'youtube', id: 'dQw4w9WgXcQ' }} />
|
|
32
|
-
<VideoPlayer source={{ type: 'vimeo', id: '123456789' }} />
|
|
14
|
+
// Raw URL — auto-classified (YouTube / Vimeo / HLS / MP4 / iframe)
|
|
15
|
+
<VideoPlayer source="https://youtu.be/sGbxmsDFVnE?t=90" />
|
|
33
16
|
```
|
|
34
17
|
|
|
35
|
-
|
|
18
|
+
## Why media-chrome
|
|
36
19
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
20
|
+
Native HTML5 `<video>` can't play YouTube. Hand-rolling the YouTube
|
|
21
|
+
IFrame API and keeping it in sync with a custom control bar is fragile.
|
|
22
|
+
media-chrome solves both: its `<media-controller>` speaks one protocol,
|
|
23
|
+
and provider elements (`<youtube-video>`, `<vimeo-video>`,
|
|
24
|
+
`<hls-video>`) plug into the same slot. Our control parts are thin
|
|
25
|
+
React wrappers themed through CSS custom properties.
|
|
41
26
|
|
|
42
|
-
|
|
27
|
+
## Sources
|
|
43
28
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```
|
|
29
|
+
`VideoSource` is a discriminated union — pass an object, or a raw URL
|
|
30
|
+
string that `parseEmbedUrl` classifies for you.
|
|
47
31
|
|
|
48
|
-
|
|
32
|
+
| `type` | Shape | Engine |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `url` | `{ type:'url', url, mimeType?, poster?, title? }` | native `<video>` |
|
|
35
|
+
| `youtube` | `{ type:'youtube', videoId, startTime?, playlistId?, poster?, title? }` | `youtube-video-element` |
|
|
36
|
+
| `vimeo` | `{ type:'vimeo', videoId, startTime?, poster?, title? }` | `vimeo-video-element` |
|
|
37
|
+
| `hls` | `{ type:'hls', url, poster?, title? }` | `hls-video-element` (native in Safari, `hls.js` elsewhere) |
|
|
38
|
+
| `iframe` | `{ type:'iframe', url, allow?, poster?, title? }` | plain `<iframe>` — control bar auto-hidden |
|
|
49
39
|
|
|
50
40
|
```tsx
|
|
51
|
-
|
|
52
|
-
<VideoPlayer
|
|
53
|
-
source={{
|
|
54
|
-
type: 'stream',
|
|
55
|
-
sessionId: 'abc123',
|
|
56
|
-
path: '/videos/movie.mp4',
|
|
57
|
-
getStreamUrl: (id, path) => `/api/stream/${id}?path=${path}&token=${token}`
|
|
58
|
-
}}
|
|
59
|
-
/>
|
|
41
|
+
import { parseEmbedUrl } from '@djangocfg/ui-tools/video-player';
|
|
60
42
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<VideoPlayer source={{ type: 'stream', path: '/videos/movie.mp4' }} />
|
|
64
|
-
</VideoPlayerProvider>
|
|
65
|
-
```
|
|
43
|
+
parseEmbedUrl('https://www.youtube.com/watch?v=ID&t=42s');
|
|
44
|
+
// → { type: 'youtube', videoId: 'ID', startTime: 42 }
|
|
66
45
|
|
|
67
|
-
|
|
46
|
+
parseEmbedUrl('https://vimeo.com/76979871');
|
|
47
|
+
// → { type: 'vimeo', videoId: '76979871' }
|
|
68
48
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
source={{
|
|
72
|
-
type: 'blob',
|
|
73
|
-
data: arrayBuffer,
|
|
74
|
-
mimeType: 'video/mp4'
|
|
75
|
-
}}
|
|
76
|
-
/>
|
|
49
|
+
parseEmbedUrl('https://stream.mux.com/abc.m3u8');
|
|
50
|
+
// → { type: 'hls', url: '…' }
|
|
77
51
|
```
|
|
78
52
|
|
|
79
53
|
## Props
|
|
80
54
|
|
|
81
|
-
| Prop | Type | Default |
|
|
82
|
-
|
|
83
|
-
| `source` | `
|
|
84
|
-
| `
|
|
85
|
-
| `aspectRatio` | `number \| 'auto' \| 'fill'` | `16/9` |
|
|
86
|
-
| `autoPlay` | `boolean` | `false` |
|
|
87
|
-
| `muted`
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `theme` | `'default' \| 'minimal' \| 'modern'` | `'default'` | Vidstack theme |
|
|
93
|
-
| `errorFallback` | `ReactNode \| (props) => ReactNode` | - | Custom error UI |
|
|
94
|
-
| `className` | `string` | - | Container className |
|
|
95
|
-
| `videoClassName` | `string` | - | Video element className |
|
|
96
|
-
|
|
97
|
-
## Events
|
|
98
|
-
|
|
99
|
-
| Event | Type | Description |
|
|
100
|
-
|-------|------|-------------|
|
|
101
|
-
| `onPlay` | `() => void` | Playback started |
|
|
102
|
-
| `onPause` | `() => void` | Playback paused |
|
|
103
|
-
| `onEnded` | `() => void` | Playback ended |
|
|
104
|
-
| `onError` | `(error: string) => void` | Error occurred |
|
|
105
|
-
| `onLoadStart` | `() => void` | Loading started |
|
|
106
|
-
| `onCanPlay` | `() => void` | Ready to play |
|
|
107
|
-
| `onTimeUpdate` | `(time: number, duration: number) => void` | Time updated |
|
|
108
|
-
|
|
109
|
-
## Ref API
|
|
110
|
-
|
|
111
|
-
```tsx
|
|
112
|
-
const playerRef = useRef<VideoPlayerRef>(null);
|
|
113
|
-
|
|
114
|
-
<VideoPlayer ref={playerRef} source={source} />
|
|
115
|
-
|
|
116
|
-
// Control methods
|
|
117
|
-
playerRef.current?.play();
|
|
118
|
-
playerRef.current?.pause();
|
|
119
|
-
playerRef.current?.seek(30); // Seek to 30 seconds
|
|
120
|
-
playerRef.current?.setVolume(0.5); // 0-1
|
|
121
|
-
playerRef.current?.setMuted(true);
|
|
122
|
-
playerRef.current?.reload();
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Error Handling
|
|
126
|
-
|
|
127
|
-
### Custom Error Fallback
|
|
55
|
+
| Prop | Type | Default | Notes |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `source` | `VideoSource \| string` | — | Object or raw URL (auto-parsed). |
|
|
58
|
+
| `controls` | `boolean` | `true` | `false` → no built-in control bar. |
|
|
59
|
+
| `aspectRatio` | `number \| 'auto' \| 'fill'` | `16/9` | `'fill'` stretches to parent height; `'auto'` keeps intrinsic. |
|
|
60
|
+
| `autoPlay` | `boolean` | `false` | Browsers require `muted` for autoplay to start. |
|
|
61
|
+
| `muted` / `loop` / `playsInline` | `boolean` | — | Forwarded to the engine. |
|
|
62
|
+
| `preload` | `'none' \| 'metadata' \| 'auto'` | — | Native sources only. |
|
|
63
|
+
| `crossOrigin` | `'' \| 'anonymous' \| 'use-credentials'` | `'anonymous'` | Native sources only. |
|
|
64
|
+
| `className` | `string` | — | On the `<MediaController>` wrapper. |
|
|
65
|
+
| `children` | `ReactNode` | — | Replaces the default control bar entirely. |
|
|
128
66
|
|
|
129
|
-
|
|
130
|
-
<VideoPlayer
|
|
131
|
-
source={source}
|
|
132
|
-
errorFallback={(props) => (
|
|
133
|
-
<div>
|
|
134
|
-
<p>Error: {props.error}</p>
|
|
135
|
-
<button onClick={props.retry}>Retry</button>
|
|
136
|
-
</div>
|
|
137
|
-
)}
|
|
138
|
-
/>
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Pre-built Error Fallback with Download
|
|
142
|
-
|
|
143
|
-
```tsx
|
|
144
|
-
import { VideoErrorFallback } from '@djangocfg/ui-nextjs';
|
|
145
|
-
|
|
146
|
-
<VideoPlayer
|
|
147
|
-
source={source}
|
|
148
|
-
errorFallback={(props) => (
|
|
149
|
-
<VideoErrorFallback
|
|
150
|
-
{...props}
|
|
151
|
-
downloadUrl={getDownloadUrl()}
|
|
152
|
-
downloadFilename="video.mp4"
|
|
153
|
-
fileSize="125 MB"
|
|
154
|
-
/>
|
|
155
|
-
)}
|
|
156
|
-
/>
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## Fill Mode
|
|
160
|
-
|
|
161
|
-
Use `aspectRatio="fill"` to fill the parent container:
|
|
162
|
-
|
|
163
|
-
```tsx
|
|
164
|
-
<div className="absolute inset-0">
|
|
165
|
-
<VideoPlayer source={source} aspectRatio="fill" />
|
|
166
|
-
</div>
|
|
167
|
-
```
|
|
67
|
+
## Composable layout
|
|
168
68
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
For apps with multiple streaming videos, use the context to avoid repetition:
|
|
172
|
-
|
|
173
|
-
```tsx
|
|
174
|
-
// In layout or parent component
|
|
175
|
-
<VideoPlayerProvider
|
|
176
|
-
sessionId={sessionId}
|
|
177
|
-
getStreamUrl={(id, path) => `/api/stream/${id}?path=${path}`}
|
|
178
|
-
>
|
|
179
|
-
{/* All nested VideoPlayers use this config */}
|
|
180
|
-
<VideoPlayer source={{ type: 'stream', path: '/video1.mp4' }} />
|
|
181
|
-
<VideoPlayer source={{ type: 'stream', path: '/video2.mp4' }} />
|
|
182
|
-
</VideoPlayerProvider>
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## File Source Helper
|
|
186
|
-
|
|
187
|
-
For file browser integration:
|
|
69
|
+
Bring your own control bar by passing `children`. Compose any
|
|
70
|
+
media-chrome element or our restyled parts:
|
|
188
71
|
|
|
189
72
|
```tsx
|
|
190
|
-
import {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
73
|
+
import {
|
|
74
|
+
VideoPlayer, ControlsBar, PlayButton, SeekBar,
|
|
75
|
+
Volume, PlaybackRate, Pip, Fullscreen,
|
|
76
|
+
} from '@djangocfg/ui-tools/video-player';
|
|
77
|
+
|
|
78
|
+
<VideoPlayer source={{ type: 'url', url: '/clip.mp4' }}>
|
|
79
|
+
<ControlsBar>
|
|
80
|
+
<PlayButton />
|
|
81
|
+
<SeekBar />
|
|
82
|
+
<Volume />
|
|
83
|
+
<PlaybackRate />
|
|
84
|
+
<Pip />
|
|
85
|
+
<Fullscreen />
|
|
86
|
+
</ControlsBar>
|
|
87
|
+
</VideoPlayer>
|
|
201
88
|
```
|
|
202
89
|
|
|
203
|
-
##
|
|
90
|
+
## Parts
|
|
204
91
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
| { type: 'vimeo'; id: string; title?: string; poster?: string }
|
|
210
|
-
| { type: 'hls'; url: string; title?: string; poster?: string }
|
|
211
|
-
| { type: 'dash'; url: string; title?: string; poster?: string }
|
|
212
|
-
| { type: 'stream'; sessionId: string; path: string; getStreamUrl: fn; mimeType?: string }
|
|
213
|
-
| { type: 'blob'; data: ArrayBuffer | Blob; mimeType?: string }
|
|
214
|
-
| { type: 'data-url'; data: string; mimeType?: string }
|
|
215
|
-
```
|
|
92
|
+
`PlayButton` · `SeekBar` · `Volume` · `PlaybackRate` · `Pip` ·
|
|
93
|
+
`Fullscreen` · `ControlsBar` · `Poster` — thin wrappers over
|
|
94
|
+
media-chrome components, restyled through semantic tokens. Each accepts
|
|
95
|
+
the props of the media-chrome element it wraps.
|
|
216
96
|
|
|
217
|
-
##
|
|
97
|
+
## Theming
|
|
218
98
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
│ ├── index.ts # Type re-exports
|
|
224
|
-
│ ├── sources.ts # Source types (Url, YouTube, HLS, etc.)
|
|
225
|
-
│ ├── player.ts # Player props, ref, events
|
|
226
|
-
│ └── provider.ts # Provider & context types
|
|
227
|
-
├── hooks/
|
|
228
|
-
│ ├── index.ts
|
|
229
|
-
│ └── useVideoPositionCache.ts # Playback position caching
|
|
230
|
-
├── utils/
|
|
231
|
-
│ ├── index.ts
|
|
232
|
-
│ ├── resolvers.ts # resolvePlayerMode, isSimpleStreamSource
|
|
233
|
-
│ └── fileSource.ts # resolveFileSource helper
|
|
234
|
-
├── components/
|
|
235
|
-
│ ├── index.ts
|
|
236
|
-
│ ├── VideoPlayer.tsx # Main orchestrator
|
|
237
|
-
│ ├── VideoControls.tsx # Standalone Vidstack controls
|
|
238
|
-
│ └── VideoErrorFallback.tsx # Pre-built error UI
|
|
239
|
-
├── context/
|
|
240
|
-
│ ├── index.ts
|
|
241
|
-
│ └── VideoPlayerContext.tsx # Streaming config provider
|
|
242
|
-
└── providers/
|
|
243
|
-
├── index.ts
|
|
244
|
-
├── VidstackProvider.tsx # YouTube, Vimeo, HLS, DASH
|
|
245
|
-
├── NativeProvider.tsx # HTML5 <video>
|
|
246
|
-
└── StreamProvider.tsx # HTTP Range, Blob
|
|
247
|
-
```
|
|
99
|
+
media-chrome reads CSS custom properties; `styles/video-player.css`
|
|
100
|
+
binds them to ui-core semantic tokens (`--primary`, `--popover`, …) via
|
|
101
|
+
`color-mix`, so the player follows light/dark and any active preset.
|
|
102
|
+
The video surface itself stays dark (`bg-black`) by convention.
|
|
248
103
|
|
|
249
|
-
##
|
|
104
|
+
## Notes
|
|
250
105
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
-
|
|
254
|
-
|
|
255
|
-
-
|
|
106
|
+
- **YouTube/Vimeo branding** — embed providers enforce a minimal logo;
|
|
107
|
+
`modestbranding` / `rel=0` reduce it but it cannot be fully removed.
|
|
108
|
+
- **PiP over YouTube** — depends on the embed; the button hides itself
|
|
109
|
+
when the engine reports no support.
|
|
110
|
+
- **HLS** — native in Safari; elsewhere `hls-video-element` lazy-loads
|
|
111
|
+
`hls.js` (~110 KB) on first HLS mount only.
|
|
112
|
+
- **`LazyVideoPlayer`** — kept as a synchronous alias of `VideoPlayer`
|
|
113
|
+
for back-compat. No lazy boundary is needed; engines tree-shake per
|
|
114
|
+
source type.
|
|
256
115
|
|
|
257
|
-
##
|
|
116
|
+
## Storybook
|
|
258
117
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
- iOS Safari 12+
|
|
264
|
-
- Chrome Android 63+
|
|
118
|
+
`UI Tools / Video Player / Player` — `NativeMp4`, `YouTubeStarWars`,
|
|
119
|
+
`YouTubeStarTrek`, `YouTubeAutoParseUrl`, `YouTubeWithTimestamp`,
|
|
120
|
+
`Vimeo`, `Sintel`, `HlsStream`, `ComposableLayout`, `Fill`,
|
|
121
|
+
`NoControls`, `LazyAlias`.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VideoPlayer — composable shell over `media-chrome` web components.
|
|
5
|
+
*
|
|
6
|
+
* The shell is just a `<MediaController>` wrapper + a `<CanvasDispatcher>`
|
|
7
|
+
* that mounts the right media element (`<video>`, `<youtube-video>`,
|
|
8
|
+
* `<vimeo-video>`, `<hls-video>`, `<iframe>`) into the `media` slot.
|
|
9
|
+
*
|
|
10
|
+
* Custom layouts: pass `children` to replace the default control bar.
|
|
11
|
+
* `controls={false}` opts out of any control surface (useful for
|
|
12
|
+
* iframe sources where the embed renders its own UI).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useMemo, type CSSProperties } from 'react';
|
|
16
|
+
import { MediaController } from 'media-chrome/react';
|
|
17
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
18
|
+
import './styles/video-player.css';
|
|
19
|
+
|
|
20
|
+
import type { VideoPlayerProps, AspectRatioValue } from './types';
|
|
21
|
+
import { parseEmbedUrl } from './utils/parse-embed-url';
|
|
22
|
+
import { CanvasDispatcher } from './canvas/canvas-dispatcher';
|
|
23
|
+
import {
|
|
24
|
+
ControlsBar,
|
|
25
|
+
PlayButton,
|
|
26
|
+
SeekBar,
|
|
27
|
+
Volume,
|
|
28
|
+
PlaybackRate,
|
|
29
|
+
Pip,
|
|
30
|
+
Fullscreen,
|
|
31
|
+
} from './parts';
|
|
32
|
+
|
|
33
|
+
function aspectRatioStyle(ar: AspectRatioValue): CSSProperties | undefined {
|
|
34
|
+
if (ar === 'fill') return { height: '100%' };
|
|
35
|
+
if (ar === 'auto') return undefined;
|
|
36
|
+
return { aspectRatio: String(ar) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function VideoPlayer({
|
|
40
|
+
source,
|
|
41
|
+
controls = true,
|
|
42
|
+
aspectRatio = 16 / 9,
|
|
43
|
+
className,
|
|
44
|
+
children,
|
|
45
|
+
...settings
|
|
46
|
+
}: VideoPlayerProps) {
|
|
47
|
+
const normalized = useMemo(
|
|
48
|
+
() => (typeof source === 'string' ? parseEmbedUrl(source) : source),
|
|
49
|
+
[source],
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const isIframe = normalized.type === 'iframe';
|
|
53
|
+
// For iframe embeds media-chrome cannot drive the inner player — hide the
|
|
54
|
+
// control bar to avoid a non-functional UI.
|
|
55
|
+
const showControls = controls && !isIframe;
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<MediaController
|
|
59
|
+
// Fade controls + scrim after 2.5s of inactivity while playing;
|
|
60
|
+
// they reappear on mousemove / pause / focus (media-chrome built-in).
|
|
61
|
+
autohide="2.5"
|
|
62
|
+
className={cn(
|
|
63
|
+
'video-player relative block w-full overflow-hidden rounded-lg bg-black',
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
style={aspectRatioStyle(aspectRatio)}
|
|
67
|
+
>
|
|
68
|
+
<CanvasDispatcher source={normalized} {...settings} />
|
|
69
|
+
{children ??
|
|
70
|
+
(showControls && (
|
|
71
|
+
<ControlsBar>
|
|
72
|
+
<PlayButton />
|
|
73
|
+
<SeekBar />
|
|
74
|
+
<Volume iconOnly />
|
|
75
|
+
<PlaybackRate />
|
|
76
|
+
<Pip />
|
|
77
|
+
<Fullscreen />
|
|
78
|
+
</ControlsBar>
|
|
79
|
+
))}
|
|
80
|
+
</MediaController>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dispatches `source.type` to the right canvas. Single-switch indirection
|
|
5
|
+
* keeps `<VideoPlayer>` simple and isolates engine-specific imports inside
|
|
6
|
+
* the canvas files so tree-shakers can drop unused providers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { VideoSource, VideoPlayerSettings } from '../types';
|
|
10
|
+
import { NativeCanvas } from './native-canvas';
|
|
11
|
+
import { YouTubeCanvas } from './youtube-canvas';
|
|
12
|
+
import { VimeoCanvas } from './vimeo-canvas';
|
|
13
|
+
import { HlsCanvas } from './hls-canvas';
|
|
14
|
+
import { IframeCanvas } from './iframe-canvas';
|
|
15
|
+
|
|
16
|
+
export interface CanvasDispatcherProps extends VideoPlayerSettings {
|
|
17
|
+
readonly source: VideoSource;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function CanvasDispatcher({ source, ...settings }: CanvasDispatcherProps) {
|
|
21
|
+
switch (source.type) {
|
|
22
|
+
case 'youtube':
|
|
23
|
+
return <YouTubeCanvas source={source} {...settings} />;
|
|
24
|
+
case 'vimeo':
|
|
25
|
+
return <VimeoCanvas source={source} {...settings} />;
|
|
26
|
+
case 'hls':
|
|
27
|
+
return <HlsCanvas source={source} {...settings} />;
|
|
28
|
+
case 'iframe':
|
|
29
|
+
return <IframeCanvas source={source} />;
|
|
30
|
+
case 'url':
|
|
31
|
+
default:
|
|
32
|
+
return <NativeCanvas source={source} {...settings} />;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<hls-video slot="media">` — HLS (`.m3u8`) playback via
|
|
5
|
+
* `hls-video-element`, which lazy-loads `hls.js` only on browsers that
|
|
6
|
+
* don't ship native HLS (everything except Safari).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import 'hls-video-element';
|
|
10
|
+
import './jsx-augmentation';
|
|
11
|
+
import { MediaPosterImage } from 'media-chrome/react';
|
|
12
|
+
import type { HlsSource, VideoPlayerSettings } from '../types';
|
|
13
|
+
|
|
14
|
+
export interface HlsCanvasProps extends VideoPlayerSettings {
|
|
15
|
+
readonly source: HlsSource;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function HlsCanvas({
|
|
19
|
+
source,
|
|
20
|
+
autoPlay,
|
|
21
|
+
muted,
|
|
22
|
+
loop,
|
|
23
|
+
crossOrigin = 'anonymous',
|
|
24
|
+
}: HlsCanvasProps) {
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<hls-video
|
|
28
|
+
slot="media"
|
|
29
|
+
src={source.url}
|
|
30
|
+
crossOrigin={crossOrigin}
|
|
31
|
+
{...(muted && { muted: true })}
|
|
32
|
+
{...(autoPlay && { autoplay: true })}
|
|
33
|
+
{...(loop && { loop: true })}
|
|
34
|
+
playsInline
|
|
35
|
+
/>
|
|
36
|
+
{source.poster && <MediaPosterImage slot="poster" src={source.poster} />}
|
|
37
|
+
</>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fallback `<iframe>` for arbitrary embed URLs that don't fit a known
|
|
5
|
+
* provider. Rendered *outside* the media-chrome controls (no
|
|
6
|
+
* `slot="media"`) because there is no JS bridge between the iframe
|
|
7
|
+
* contents and `<MediaController>`. Consumers should pass
|
|
8
|
+
* `controls={false}` for iframe sources, or live with a non-functional
|
|
9
|
+
* control bar — we hide it via CSS in `VideoPlayer.tsx`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { IframeSource } from '../types';
|
|
13
|
+
|
|
14
|
+
export interface IframeCanvasProps {
|
|
15
|
+
readonly source: IframeSource;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DEFAULT_ALLOW =
|
|
19
|
+
'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share';
|
|
20
|
+
|
|
21
|
+
export function IframeCanvas({ source }: IframeCanvasProps) {
|
|
22
|
+
return (
|
|
23
|
+
<iframe
|
|
24
|
+
slot="media"
|
|
25
|
+
src={source.url}
|
|
26
|
+
title={source.title ?? 'Embedded video'}
|
|
27
|
+
allow={source.allow ?? DEFAULT_ALLOW}
|
|
28
|
+
allowFullScreen
|
|
29
|
+
referrerPolicy="strict-origin-when-cross-origin"
|
|
30
|
+
className="absolute inset-0 h-full w-full border-0"
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { CanvasDispatcher } from './canvas-dispatcher';
|
|
2
|
+
export type { CanvasDispatcherProps } from './canvas-dispatcher';
|
|
3
|
+
export { NativeCanvas } from './native-canvas';
|
|
4
|
+
export type { NativeCanvasProps } from './native-canvas';
|
|
5
|
+
export { YouTubeCanvas } from './youtube-canvas';
|
|
6
|
+
export type { YouTubeCanvasProps } from './youtube-canvas';
|
|
7
|
+
export { VimeoCanvas } from './vimeo-canvas';
|
|
8
|
+
export type { VimeoCanvasProps } from './vimeo-canvas';
|
|
9
|
+
export { HlsCanvas } from './hls-canvas';
|
|
10
|
+
export type { HlsCanvasProps } from './hls-canvas';
|
|
11
|
+
export { IframeCanvas } from './iframe-canvas';
|
|
12
|
+
export type { IframeCanvasProps } from './iframe-canvas';
|