@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
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { useMemo } from 'react';
|
|
8
8
|
import { ZoomIn, ZoomOut, RotateCw, FlipHorizontal, FlipVertical, Maximize2, Expand } from 'lucide-react';
|
|
9
9
|
import { useControls } from 'react-zoom-pan-pinch';
|
|
10
|
-
import {
|
|
10
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@djangocfg/ui-core/components';
|
|
11
11
|
import { useAppT } from '@djangocfg/i18n';
|
|
12
12
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
13
13
|
import { ZOOM_PRESETS } from '../utils';
|
|
@@ -17,6 +17,18 @@ import type { ImageToolbarProps } from '../types';
|
|
|
17
17
|
// COMPONENT
|
|
18
18
|
// =============================================================================
|
|
19
19
|
|
|
20
|
+
// The toolbar floats over the image, whose brightness is unknown and
|
|
21
|
+
// unrelated to the host theme. A theme-bound `bg-background` surface
|
|
22
|
+
// would clash with a dark photo (white slab) or a light photo in dark
|
|
23
|
+
// mode. A fixed dark-glass overlay with light icons stays readable on
|
|
24
|
+
// any content — the same model the gallery nav arrows and media
|
|
25
|
+
// players use.
|
|
26
|
+
const TB_BUTTON =
|
|
27
|
+
'inline-flex items-center justify-center rounded-md text-white/90 ' +
|
|
28
|
+
'transition-colors hover:bg-white/15 hover:text-white ' +
|
|
29
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70 ' +
|
|
30
|
+
'disabled:opacity-40 disabled:pointer-events-none';
|
|
31
|
+
|
|
20
32
|
export function ImageToolbar({
|
|
21
33
|
scale,
|
|
22
34
|
transform,
|
|
@@ -45,23 +57,25 @@ export function ImageToolbar({
|
|
|
45
57
|
}, [scale]);
|
|
46
58
|
|
|
47
59
|
return (
|
|
48
|
-
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-0.5 bg-
|
|
60
|
+
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-0.5 bg-black/60 backdrop-blur-sm border border-white/10 rounded-lg p-1 shadow-lg">
|
|
49
61
|
{/* Zoom controls */}
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
className="h-7 w-7"
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
className={cn(TB_BUTTON, 'h-7 w-7')}
|
|
54
65
|
onClick={() => zoomOut()}
|
|
55
66
|
title={labels.zoomOut}
|
|
56
67
|
>
|
|
57
68
|
<ZoomOut className="h-3.5 w-3.5" />
|
|
58
|
-
</
|
|
69
|
+
</button>
|
|
59
70
|
|
|
60
71
|
<DropdownMenu>
|
|
61
72
|
<DropdownMenuTrigger asChild>
|
|
62
|
-
<
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
className={cn(TB_BUTTON, 'h-7 px-2 min-w-[52px] font-mono text-xs')}
|
|
76
|
+
>
|
|
63
77
|
{zoomLabel}
|
|
64
|
-
</
|
|
78
|
+
</button>
|
|
65
79
|
</DropdownMenuTrigger>
|
|
66
80
|
<DropdownMenuContent align="center" className="min-w-[80px]">
|
|
67
81
|
{ZOOM_PRESETS.map((preset) => (
|
|
@@ -76,80 +90,74 @@ export function ImageToolbar({
|
|
|
76
90
|
</DropdownMenuContent>
|
|
77
91
|
</DropdownMenu>
|
|
78
92
|
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
className="h-7 w-7"
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
className={cn(TB_BUTTON, 'h-7 w-7')}
|
|
83
96
|
onClick={() => zoomIn()}
|
|
84
97
|
title={labels.zoomIn}
|
|
85
98
|
>
|
|
86
99
|
<ZoomIn className="h-3.5 w-3.5" />
|
|
87
|
-
</
|
|
100
|
+
</button>
|
|
88
101
|
|
|
89
|
-
<div className="w-px h-4 bg-
|
|
102
|
+
<div className="w-px h-4 bg-white/20 mx-1" />
|
|
90
103
|
|
|
91
104
|
{/* Fit to view */}
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
className="h-7 w-7"
|
|
105
|
+
<button
|
|
106
|
+
type="button"
|
|
107
|
+
className={cn(TB_BUTTON, 'h-7 w-7')}
|
|
96
108
|
onClick={() => resetTransform()}
|
|
97
109
|
title={labels.fitToView}
|
|
98
110
|
>
|
|
99
111
|
<Maximize2 className="h-3.5 w-3.5" />
|
|
100
|
-
</
|
|
112
|
+
</button>
|
|
101
113
|
|
|
102
|
-
<div className="w-px h-4 bg-
|
|
114
|
+
<div className="w-px h-4 bg-white/20 mx-1" />
|
|
103
115
|
|
|
104
116
|
{/* Transform controls */}
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
className={cn('h-7 w-7', transform.flipH && 'bg-accent')}
|
|
117
|
+
<button
|
|
118
|
+
type="button"
|
|
119
|
+
className={cn(TB_BUTTON, 'h-7 w-7', transform.flipH && 'bg-white/20 text-white')}
|
|
109
120
|
onClick={onFlipH}
|
|
110
121
|
title={labels.flipHorizontal}
|
|
111
122
|
>
|
|
112
123
|
<FlipHorizontal className="h-3.5 w-3.5" />
|
|
113
|
-
</
|
|
124
|
+
</button>
|
|
114
125
|
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
className={cn('h-7 w-7', transform.flipV && 'bg-accent')}
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
className={cn(TB_BUTTON, 'h-7 w-7', transform.flipV && 'bg-white/20 text-white')}
|
|
119
129
|
onClick={onFlipV}
|
|
120
130
|
title={labels.flipVertical}
|
|
121
131
|
>
|
|
122
132
|
<FlipVertical className="h-3.5 w-3.5" />
|
|
123
|
-
</
|
|
133
|
+
</button>
|
|
124
134
|
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
className="h-7 w-7"
|
|
135
|
+
<button
|
|
136
|
+
type="button"
|
|
137
|
+
className={cn(TB_BUTTON, 'h-7 w-7')}
|
|
129
138
|
onClick={onRotate}
|
|
130
139
|
title={labels.rotate}
|
|
131
140
|
>
|
|
132
141
|
<RotateCw className="h-3.5 w-3.5" />
|
|
133
|
-
</
|
|
142
|
+
</button>
|
|
134
143
|
|
|
135
144
|
{transform.rotation !== 0 && (
|
|
136
|
-
<span className="text-[10px] text-
|
|
145
|
+
<span className="text-[10px] text-white/60 font-mono pl-1">
|
|
137
146
|
{transform.rotation}°
|
|
138
147
|
</span>
|
|
139
148
|
)}
|
|
140
149
|
|
|
141
150
|
{onExpand && (
|
|
142
151
|
<>
|
|
143
|
-
<div className="w-px h-4 bg-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
className="h-7 w-7"
|
|
152
|
+
<div className="w-px h-4 bg-white/20 mx-1" />
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
className={cn(TB_BUTTON, 'h-7 w-7')}
|
|
148
156
|
onClick={onExpand}
|
|
149
157
|
title={labels.fullscreen}
|
|
150
158
|
>
|
|
151
159
|
<Expand className="h-3.5 w-3.5" />
|
|
152
|
-
</
|
|
160
|
+
</button>
|
|
153
161
|
</>
|
|
154
162
|
)}
|
|
155
163
|
</div>
|
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
|
17
|
-
import { ImageIcon, AlertCircle, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
18
|
-
import {
|
|
17
|
+
import { ImageIcon, AlertCircle, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react';
|
|
18
|
+
import {
|
|
19
|
+
TransformWrapper,
|
|
20
|
+
TransformComponent,
|
|
21
|
+
type ReactZoomPanPinchRef,
|
|
22
|
+
} from 'react-zoom-pan-pinch';
|
|
19
23
|
import { cn, Dialog, DialogContent, DialogTitle, Alert, AlertDescription } from '@djangocfg/ui-core';
|
|
20
24
|
import { useAppT } from '@djangocfg/i18n';
|
|
21
25
|
import { useHotkey } from '@djangocfg/ui-core/hooks';
|
|
@@ -23,7 +27,8 @@ import { useHotkey } from '@djangocfg/ui-core/hooks';
|
|
|
23
27
|
import { ImageToolbar } from './ImageToolbar';
|
|
24
28
|
import { ImageInfo } from './ImageInfo';
|
|
25
29
|
import { useImageTransform, useImageLoading } from '../hooks';
|
|
26
|
-
import
|
|
30
|
+
import { KEYBOARD_PAN_STEP } from '../utils';
|
|
31
|
+
import type { ImageViewerProps } from '../types';
|
|
27
32
|
|
|
28
33
|
// =============================================================================
|
|
29
34
|
// COMPONENT
|
|
@@ -51,13 +56,16 @@ export function ImageViewer({
|
|
|
51
56
|
const [scale, setScale] = useState(1);
|
|
52
57
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
53
58
|
const [loadError, setLoadError] = useState(false);
|
|
59
|
+
const [imgDecoded, setImgDecoded] = useState(false);
|
|
54
60
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
55
|
-
const controlsRef = useRef<
|
|
61
|
+
const controlsRef = useRef<ReactZoomPanPinchRef | null>(null);
|
|
56
62
|
|
|
57
63
|
const labels = useMemo(() => ({
|
|
58
64
|
noImage: t('tools.image.noImage'),
|
|
59
65
|
failedToLoad: t('tools.image.failedToLoad'),
|
|
60
66
|
loading: t('ui.form.loading'),
|
|
67
|
+
prev: t('tools.gallery.previous'),
|
|
68
|
+
next: t('tools.gallery.next'),
|
|
61
69
|
}), [t]);
|
|
62
70
|
|
|
63
71
|
const {
|
|
@@ -73,16 +81,34 @@ export function ImageViewer({
|
|
|
73
81
|
src: current?.src,
|
|
74
82
|
});
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
// Reset per-source load flags whenever the source changes
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
setLoadError(false);
|
|
87
|
+
setImgDecoded(false);
|
|
88
|
+
}, [src]);
|
|
77
89
|
|
|
78
90
|
const { transform, rotate, flipH, flipV, transformStyle } = useImageTransform({
|
|
79
91
|
resetKey: current?.file.path ?? '',
|
|
80
92
|
});
|
|
81
93
|
|
|
82
94
|
const handleZoomPreset = useCallback((value: number | 'fit') => {
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
95
|
+
const controls = controlsRef.current;
|
|
96
|
+
if (!controls) return;
|
|
97
|
+
if (value === 'fit') {
|
|
98
|
+
controls.resetTransform();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Anchor preset zoom to the canvas center instead of the top-left origin
|
|
102
|
+
const el = controls.instance.wrapperComponent;
|
|
103
|
+
if (el) {
|
|
104
|
+
const cx = el.offsetWidth / 2;
|
|
105
|
+
const cy = el.offsetHeight / 2;
|
|
106
|
+
const x = cx - cx * value;
|
|
107
|
+
const y = cy - cy * value;
|
|
108
|
+
controls.setTransform(x, y, value);
|
|
109
|
+
} else {
|
|
110
|
+
controls.setTransform(0, 0, value);
|
|
111
|
+
}
|
|
86
112
|
}, []);
|
|
87
113
|
|
|
88
114
|
const handleExpand = useCallback(() => setDialogOpen(true), []);
|
|
@@ -97,7 +123,18 @@ export function ImageViewer({
|
|
|
97
123
|
[images.length]
|
|
98
124
|
);
|
|
99
125
|
|
|
100
|
-
//
|
|
126
|
+
// Pan the image by a fixed offset (arrow keys)
|
|
127
|
+
const panBy = useCallback((dx: number, dy: number) => {
|
|
128
|
+
const controls = controlsRef.current;
|
|
129
|
+
if (!controls) return false;
|
|
130
|
+
const { positionX, positionY, scale: s } = controls.instance.transformState;
|
|
131
|
+
// Only pan when the image is zoomed in — otherwise it cannot move
|
|
132
|
+
if (s <= 1) return false;
|
|
133
|
+
controls.setTransform(positionX + dx, positionY + dy, s);
|
|
134
|
+
return true;
|
|
135
|
+
}, []);
|
|
136
|
+
|
|
137
|
+
// Keyboard: zoom / rotate / pan (only when container focused)
|
|
101
138
|
useEffect(() => {
|
|
102
139
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
103
140
|
if (!containerRef.current?.contains(document.activeElement) &&
|
|
@@ -106,18 +143,33 @@ export function ImageViewer({
|
|
|
106
143
|
if (!controls) return;
|
|
107
144
|
switch (e.key) {
|
|
108
145
|
case '+': case '=': e.preventDefault(); controls.zoomIn(); break;
|
|
109
|
-
case '-': e.preventDefault(); controls.zoomOut(); break;
|
|
146
|
+
case '-': case '_': e.preventDefault(); controls.zoomOut(); break;
|
|
110
147
|
case '0': e.preventDefault(); controls.resetTransform(); break;
|
|
111
|
-
case 'r':
|
|
148
|
+
case 'r': case 'R':
|
|
149
|
+
if (!e.metaKey && !e.ctrlKey) { e.preventDefault(); rotate(); }
|
|
150
|
+
break;
|
|
151
|
+
// Arrow keys pan only when zoomed in. While at fit scale they
|
|
152
|
+
// fall through to gallery navigation (handled by useHotkey).
|
|
153
|
+
case 'ArrowUp': if (panBy(0, KEYBOARD_PAN_STEP)) e.preventDefault(); break;
|
|
154
|
+
case 'ArrowDown': if (panBy(0, -KEYBOARD_PAN_STEP)) e.preventDefault(); break;
|
|
155
|
+
case 'ArrowLeft': if (panBy(KEYBOARD_PAN_STEP, 0)) e.preventDefault(); break;
|
|
156
|
+
case 'ArrowRight': if (panBy(-KEYBOARD_PAN_STEP, 0)) e.preventDefault(); break;
|
|
112
157
|
}
|
|
113
158
|
};
|
|
114
159
|
window.addEventListener('keydown', handleKeyDown);
|
|
115
160
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
116
|
-
}, [rotate]);
|
|
161
|
+
}, [rotate, panBy]);
|
|
117
162
|
|
|
118
|
-
// Keyboard: gallery navigation (global
|
|
119
|
-
|
|
120
|
-
useHotkey('
|
|
163
|
+
// Keyboard: gallery navigation (global, only while not zoomed in so it
|
|
164
|
+
// never competes with arrow-key panning)
|
|
165
|
+
useHotkey('ArrowLeft', prev, {
|
|
166
|
+
enabled: hasMultiple && scale <= 1,
|
|
167
|
+
preventDefault: true,
|
|
168
|
+
});
|
|
169
|
+
useHotkey('ArrowRight', next, {
|
|
170
|
+
enabled: hasMultiple && scale <= 1,
|
|
171
|
+
preventDefault: true,
|
|
172
|
+
});
|
|
121
173
|
|
|
122
174
|
if (!current) {
|
|
123
175
|
return (
|
|
@@ -153,8 +205,11 @@ export function ImageViewer({
|
|
|
153
205
|
<div
|
|
154
206
|
ref={containerRef}
|
|
155
207
|
tabIndex={0}
|
|
208
|
+
role="img"
|
|
209
|
+
aria-label={current.file.name}
|
|
156
210
|
className={cn(
|
|
157
211
|
'flex-1 h-full relative overflow-hidden outline-none',
|
|
212
|
+
'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
|
|
158
213
|
'bg-[length:16px_16px]',
|
|
159
214
|
'[background-color:color-mix(in_oklab,var(--muted)_20%,transparent)]',
|
|
160
215
|
'[background-image:linear-gradient(45deg,color-mix(in_oklab,var(--muted)_40%,transparent)_25%,transparent_25%),linear-gradient(-45deg,color-mix(in_oklab,var(--muted)_40%,transparent)_25%,transparent_25%),linear-gradient(45deg,transparent_75%,color-mix(in_oklab,var(--muted)_40%,transparent)_75%),linear-gradient(-45deg,transparent_75%,color-mix(in_oklab,var(--muted)_40%,transparent)_75%)]',
|
|
@@ -164,19 +219,31 @@ export function ImageViewer({
|
|
|
164
219
|
{src && <ImageInfo src={src} />}
|
|
165
220
|
|
|
166
221
|
{useProgressiveLoading && !isFullyLoaded && (
|
|
167
|
-
<div className="absolute top-3 left-3 z-10 px-2 py-1 bg-
|
|
168
|
-
<div className="w-2 h-2 bg-blue-
|
|
222
|
+
<div className="absolute top-3 left-3 z-10 px-2 py-1 bg-black/60 backdrop-blur-sm border border-white/10 rounded text-[10px] text-white/80 font-mono flex items-center gap-1.5">
|
|
223
|
+
<div className="w-2 h-2 bg-blue-400 rounded-full animate-pulse" />
|
|
169
224
|
{labels.loading}
|
|
170
225
|
</div>
|
|
171
226
|
)}
|
|
172
227
|
|
|
228
|
+
{/* Spinner while a non-progressive image is still decoding */}
|
|
229
|
+
{!useProgressiveLoading && !imgDecoded && !loadError && (
|
|
230
|
+
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 pointer-events-none">
|
|
231
|
+
<Loader2 className="w-8 h-8 text-muted-foreground/60 animate-spin" />
|
|
232
|
+
<span className="text-xs text-muted-foreground">{labels.loading}</span>
|
|
233
|
+
</div>
|
|
234
|
+
)}
|
|
235
|
+
|
|
173
236
|
<TransformWrapper
|
|
174
237
|
initialScale={1}
|
|
175
238
|
minScale={0.1}
|
|
176
239
|
maxScale={8}
|
|
177
240
|
centerOnInit
|
|
178
241
|
centerZoomedOut
|
|
179
|
-
onTransformed={(ref, state) => {
|
|
242
|
+
onTransformed={(ref, state) => {
|
|
243
|
+
controlsRef.current = ref;
|
|
244
|
+
// Avoid a re-render on every pan frame — only when zoom changes
|
|
245
|
+
setScale((prev) => (prev === state.scale ? prev : state.scale));
|
|
246
|
+
}}
|
|
180
247
|
onInit={(ref) => { controlsRef.current = ref; }}
|
|
181
248
|
wheel={{ step: 0.1 }}
|
|
182
249
|
doubleClick={{ mode: 'toggle', step: 2 }}
|
|
@@ -196,13 +263,21 @@ export function ImageViewer({
|
|
|
196
263
|
wrapperClass="!w-full !h-full cursor-grab active:cursor-grabbing"
|
|
197
264
|
contentClass="!w-full !h-full flex items-center justify-center"
|
|
198
265
|
>
|
|
199
|
-
|
|
266
|
+
{/*
|
|
267
|
+
Fill the TransformComponent content box so the children's
|
|
268
|
+
`max-w-full / max-h-full` resolve against the actual viewport
|
|
269
|
+
instead of the image's natural box. Without `w-full h-full`
|
|
270
|
+
this wrapper shrink-fits the image, which collapses the
|
|
271
|
+
max-* constraints and renders the image at intrinsic size —
|
|
272
|
+
visible as cropping / half-height in tall containers.
|
|
273
|
+
*/}
|
|
274
|
+
<div className="relative w-full h-full flex items-center justify-center">
|
|
200
275
|
{useProgressiveLoading && lqip && !isFullyLoaded && (
|
|
201
276
|
<img
|
|
202
277
|
src={lqip}
|
|
203
278
|
alt=""
|
|
204
279
|
aria-hidden="true"
|
|
205
|
-
className="absolute
|
|
280
|
+
className="absolute max-w-full max-h-full object-contain select-none"
|
|
206
281
|
style={{ transform: transformStyle, filter: 'blur(20px)', transition: 'opacity 0.3s ease-out', opacity: isFullyLoaded ? 0 : 1 }}
|
|
207
282
|
draggable={false}
|
|
208
283
|
/>
|
|
@@ -214,11 +289,12 @@ export function ImageViewer({
|
|
|
214
289
|
className="max-w-full max-h-full object-contain select-none"
|
|
215
290
|
style={{
|
|
216
291
|
transform: transformStyle,
|
|
217
|
-
transition:
|
|
218
|
-
opacity:
|
|
292
|
+
transition: 'transform 0.15s ease-out, opacity 0.3s ease-out',
|
|
293
|
+
opacity:
|
|
294
|
+
(useProgressiveLoading && !isFullyLoaded) || !imgDecoded ? 0 : 1,
|
|
219
295
|
}}
|
|
220
296
|
draggable={false}
|
|
221
|
-
|
|
297
|
+
onLoad={() => setImgDecoded(true)}
|
|
222
298
|
onError={() => setLoadError(true)}
|
|
223
299
|
/>
|
|
224
300
|
)}
|
|
@@ -232,18 +308,22 @@ export function ImageViewer({
|
|
|
232
308
|
<button
|
|
233
309
|
type="button"
|
|
234
310
|
onClick={prev}
|
|
235
|
-
|
|
311
|
+
aria-label={labels.prev}
|
|
312
|
+
title={labels.prev}
|
|
313
|
+
className="absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
|
|
236
314
|
>
|
|
237
315
|
<ChevronLeft className="h-5 w-5" />
|
|
238
316
|
</button>
|
|
239
317
|
<button
|
|
240
318
|
type="button"
|
|
241
319
|
onClick={next}
|
|
242
|
-
|
|
320
|
+
aria-label={labels.next}
|
|
321
|
+
title={labels.next}
|
|
322
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
|
|
243
323
|
>
|
|
244
324
|
<ChevronRight className="h-5 w-5" />
|
|
245
325
|
</button>
|
|
246
|
-
<div className="absolute
|
|
326
|
+
<div className="absolute top-3 left-1/2 -translate-x-1/2 z-10 bg-black/60 backdrop-blur-sm border border-white/10 text-white/80 text-xs font-mono px-2 py-0.5 rounded-full pointer-events-none">
|
|
247
327
|
{currentIndex + 1} / {images.length}
|
|
248
328
|
</div>
|
|
249
329
|
</>
|
|
@@ -171,11 +171,16 @@ export function useImageLoading(options: UseImageLoadingOptions): UseImageLoadin
|
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
setLqip(null);
|
|
174
175
|
setIsFullyLoaded(false);
|
|
175
176
|
imageDebug.state('progressive loading', { size });
|
|
176
177
|
|
|
178
|
+
// Guard against state updates after src change / unmount
|
|
179
|
+
let cancelled = false;
|
|
180
|
+
|
|
177
181
|
// Create low-quality placeholder
|
|
178
182
|
createLQIP(src).then((placeholder) => {
|
|
183
|
+
if (cancelled || !isMountedRef.current) return;
|
|
179
184
|
if (placeholder) {
|
|
180
185
|
imageDebug.debug('LQIP created');
|
|
181
186
|
setLqip(placeholder);
|
|
@@ -185,6 +190,7 @@ export function useImageLoading(options: UseImageLoadingOptions): UseImageLoadin
|
|
|
185
190
|
// Pre-load full image
|
|
186
191
|
const img = new Image();
|
|
187
192
|
img.onload = () => {
|
|
193
|
+
if (cancelled || !isMountedRef.current) return;
|
|
188
194
|
imageDebug.state('fully loaded');
|
|
189
195
|
setIsFullyLoaded(true);
|
|
190
196
|
};
|
|
@@ -192,6 +198,13 @@ export function useImageLoading(options: UseImageLoadingOptions): UseImageLoadin
|
|
|
192
198
|
imageDebug.error('Failed to load full image');
|
|
193
199
|
};
|
|
194
200
|
img.src = src;
|
|
201
|
+
|
|
202
|
+
return () => {
|
|
203
|
+
cancelled = true;
|
|
204
|
+
img.onload = null;
|
|
205
|
+
img.onerror = null;
|
|
206
|
+
img.src = '';
|
|
207
|
+
};
|
|
195
208
|
}, [src, useProgressiveLoading, size]);
|
|
196
209
|
|
|
197
210
|
return {
|
|
@@ -37,6 +37,9 @@ export const MIN_ZOOM = 0.1;
|
|
|
37
37
|
/** Maximum zoom level */
|
|
38
38
|
export const MAX_ZOOM = 8;
|
|
39
39
|
|
|
40
|
+
/** Pixels to pan per arrow-key press */
|
|
41
|
+
export const KEYBOARD_PAN_STEP = 60;
|
|
42
|
+
|
|
40
43
|
/** Available zoom presets */
|
|
41
44
|
export const ZOOM_PRESETS: readonly ZoomPreset[] = [
|
|
42
45
|
{ label: 'Fit', value: 'fit' },
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
import { JsonFormContext, JsonSchemaFormProps } from './types';
|
|
18
18
|
import { normalizeFormData, validateSchema } from './utils';
|
|
19
19
|
import {
|
|
20
|
-
CheckboxWidget, ColorWidget, NumberWidget, SelectWidget, SliderWidget, SwitchWidget,
|
|
20
|
+
CheckboxWidget, ColorWidget, NumberWidget, RadioWidget, SelectWidget, SliderWidget, SwitchWidget,
|
|
21
|
+
TextareaWidget, TextWidget
|
|
21
22
|
} from './widgets';
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -108,6 +109,7 @@ export function JsonSchemaForm<T = any>(props: JsonSchemaFormProps<T>) {
|
|
|
108
109
|
NumberWidget,
|
|
109
110
|
CheckboxWidget,
|
|
110
111
|
SelectWidget,
|
|
112
|
+
RadioWidget,
|
|
111
113
|
SwitchWidget,
|
|
112
114
|
ColorWidget,
|
|
113
115
|
SliderWidget,
|
|
@@ -117,6 +119,7 @@ export function JsonSchemaForm<T = any>(props: JsonSchemaFormProps<T>) {
|
|
|
117
119
|
number: NumberWidget,
|
|
118
120
|
checkbox: CheckboxWidget,
|
|
119
121
|
select: SelectWidget,
|
|
122
|
+
radio: RadioWidget,
|
|
120
123
|
switch: SwitchWidget,
|
|
121
124
|
color: ColorWidget,
|
|
122
125
|
slider: SliderWidget,
|
|
@@ -4,6 +4,7 @@ import { Plus } from 'lucide-react';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
|
|
6
6
|
import { Button } from '@djangocfg/ui-core/components';
|
|
7
|
+
import { useAppT } from '@djangocfg/i18n';
|
|
7
8
|
import { ArrayFieldTemplateProps } from '@rjsf/utils';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -23,6 +24,7 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
|
|
23
24
|
onAddClick,
|
|
24
25
|
required,
|
|
25
26
|
} = props;
|
|
27
|
+
const t = useAppT();
|
|
26
28
|
|
|
27
29
|
return (
|
|
28
30
|
<div className="space-y-4">
|
|
@@ -41,7 +43,7 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
|
|
41
43
|
className="gap-2"
|
|
42
44
|
>
|
|
43
45
|
<Plus className="h-4 w-4" />
|
|
44
|
-
|
|
46
|
+
{t('tools.jsonForm.addItem')}
|
|
45
47
|
</Button>
|
|
46
48
|
)}
|
|
47
49
|
</div>
|
|
@@ -54,7 +56,7 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
|
|
54
56
|
|
|
55
57
|
{items.length === 0 && (
|
|
56
58
|
<div className="text-center py-8 text-muted-foreground border-2 border-dashed rounded-md">
|
|
57
|
-
|
|
59
|
+
{t('tools.jsonForm.noItems')}
|
|
58
60
|
{canAdd && (
|
|
59
61
|
<Button
|
|
60
62
|
type="button"
|
|
@@ -64,7 +66,7 @@ export function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
|
|
|
64
66
|
className="mt-2 gap-2"
|
|
65
67
|
>
|
|
66
68
|
<Plus className="h-4 w-4" />
|
|
67
|
-
|
|
69
|
+
{t('tools.jsonForm.addFirstItem')}
|
|
68
70
|
</Button>
|
|
69
71
|
)}
|
|
70
72
|
</div>
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
import React, { ChangeEvent, FocusEvent, useCallback, useMemo } from 'react';
|
|
4
4
|
|
|
5
5
|
import { Input } from '@djangocfg/ui-core/components';
|
|
6
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
6
7
|
import { getInputProps, WidgetProps } from '@rjsf/utils';
|
|
7
8
|
|
|
9
|
+
import { useWidgetEnv } from '../widgets/_useWidgetEnv';
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* Base input template for JSON Schema Form
|
|
10
13
|
*
|
|
@@ -24,8 +27,6 @@ export function BaseInputTemplate(props: WidgetProps) {
|
|
|
24
27
|
id,
|
|
25
28
|
type,
|
|
26
29
|
value,
|
|
27
|
-
readonly,
|
|
28
|
-
disabled,
|
|
29
30
|
autofocus,
|
|
30
31
|
onBlur,
|
|
31
32
|
onFocus,
|
|
@@ -35,6 +36,7 @@ export function BaseInputTemplate(props: WidgetProps) {
|
|
|
35
36
|
rawErrors,
|
|
36
37
|
placeholder,
|
|
37
38
|
} = props;
|
|
39
|
+
const { disabled, compact, tooltipText } = useWidgetEnv(props);
|
|
38
40
|
|
|
39
41
|
// Get input props from RJSF utils (handles step, min, max, etc.)
|
|
40
42
|
const inputProps = useMemo(() => {
|
|
@@ -92,13 +94,14 @@ export function BaseInputTemplate(props: WidgetProps) {
|
|
|
92
94
|
type={inputType}
|
|
93
95
|
value={safeValue}
|
|
94
96
|
disabled={disabled}
|
|
95
|
-
readOnly={readonly}
|
|
96
97
|
autoFocus={autofocus}
|
|
97
98
|
onChange={handleChange}
|
|
98
99
|
onBlur={handleBlur}
|
|
99
100
|
onFocus={handleFocus}
|
|
100
101
|
placeholder={placeholder}
|
|
101
|
-
|
|
102
|
+
title={tooltipText}
|
|
103
|
+
aria-invalid={hasError || undefined}
|
|
104
|
+
className={cn(hasError && 'border-destructive', compact && 'h-7 text-xs')}
|
|
102
105
|
step={inputProps.step}
|
|
103
106
|
min={inputProps.min}
|
|
104
107
|
max={inputProps.max}
|
|
@@ -4,6 +4,7 @@ import { AlertCircle } from 'lucide-react';
|
|
|
4
4
|
import React from 'react';
|
|
5
5
|
|
|
6
6
|
import { Alert, AlertDescription, AlertTitle } from '@djangocfg/ui-core/components';
|
|
7
|
+
import { useAppT } from '@djangocfg/i18n';
|
|
7
8
|
import { ErrorListProps } from '@rjsf/utils';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -12,6 +13,7 @@ import { ErrorListProps } from '@rjsf/utils';
|
|
|
12
13
|
*/
|
|
13
14
|
export function ErrorListTemplate(props: ErrorListProps) {
|
|
14
15
|
const { errors } = props;
|
|
16
|
+
const t = useAppT();
|
|
15
17
|
|
|
16
18
|
if (!errors || errors.length === 0) {
|
|
17
19
|
return null;
|
|
@@ -20,7 +22,7 @@ export function ErrorListTemplate(props: ErrorListProps) {
|
|
|
20
22
|
return (
|
|
21
23
|
<Alert variant="destructive" className="mb-6">
|
|
22
24
|
<AlertCircle className="h-4 w-4" />
|
|
23
|
-
<AlertTitle>
|
|
25
|
+
<AlertTitle>{t('tools.jsonForm.validationErrors')}</AlertTitle>
|
|
24
26
|
<AlertDescription>
|
|
25
27
|
<ul className="list-disc list-inside space-y-1 mt-2">
|
|
26
28
|
{errors.map((error, index) => (
|
|
@@ -9,6 +9,21 @@ import { ObjectFieldTemplateProps } from '@rjsf/utils';
|
|
|
9
9
|
|
|
10
10
|
import type { JsonFormDensity, UiGroup } from '../types';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Static map of literal grid-column classes.
|
|
14
|
+
*
|
|
15
|
+
* Tailwind JIT only compiles classes it can see at build time, so a
|
|
16
|
+
* runtime-built `grid-cols-${n}` string is never emitted. Listing the
|
|
17
|
+
* full literal classes here keeps `ui:grid` working. Columns collapse
|
|
18
|
+
* to a single column on small screens for responsive behaviour.
|
|
19
|
+
*/
|
|
20
|
+
const GRID_COL_CLASS: Record<number, string> = {
|
|
21
|
+
1: 'grid-cols-1',
|
|
22
|
+
2: 'grid-cols-1 sm:grid-cols-2',
|
|
23
|
+
3: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3',
|
|
24
|
+
4: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-4',
|
|
25
|
+
};
|
|
26
|
+
|
|
12
27
|
/**
|
|
13
28
|
* Object field template for JSON Schema Form
|
|
14
29
|
*
|
|
@@ -53,9 +68,14 @@ export function ObjectFieldTemplate(props: ObjectFieldTemplateProps) {
|
|
|
53
68
|
// Check if this is root object (no title usually means root)
|
|
54
69
|
const isRoot = !title;
|
|
55
70
|
|
|
56
|
-
// Grid class based on columns
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
// Grid class based on columns. `ui:grid` is clamped to the 1-4 range
|
|
72
|
+
// covered by GRID_COL_CLASS so Tailwind always has a literal class.
|
|
73
|
+
const colClass =
|
|
74
|
+
typeof gridCols === 'number'
|
|
75
|
+
? GRID_COL_CLASS[Math.min(4, Math.max(1, Math.round(gridCols)))]
|
|
76
|
+
: undefined;
|
|
77
|
+
const gridClass = colClass
|
|
78
|
+
? cn('grid', compact ? 'gap-2' : 'gap-4', colClass)
|
|
59
79
|
: compact
|
|
60
80
|
? 'space-y-2'
|
|
61
81
|
: 'space-y-4';
|