@bsol-oss/react-datatable5 12.0.0-beta.81 → 12.0.0-beta.83

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/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Image, Alert, Field as Field$1, Popover, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading, Skeleton } from '@chakra-ui/react';
2
+ import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Image, Alert, Field as Field$1, Popover, Tabs, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading, Skeleton } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
5
  import React__default, { createContext, useContext, useState, useEffect, useRef, forwardRef } from 'react';
6
- import { LuX, LuCheck, LuChevronRight, LuImage, LuFile, LuSearch } from 'react-icons/lu';
6
+ import { LuX, LuCheck, LuChevronRight, LuSearch, LuImage, LuFile } from 'react-icons/lu';
7
7
  import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineChecklist, MdClear, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdDateRange } from 'react-icons/md';
8
8
  import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
9
9
  import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
@@ -4624,10 +4624,13 @@ function formatBytes(bytes) {
4624
4624
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
4625
4625
  }
4626
4626
 
4627
- function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, labels, translate, colLabel, }) {
4627
+ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect, selectedFileId: controlledSelectedFileId, onSelectedFileIdChange, }) => {
4628
4628
  const [searchTerm, setSearchTerm] = useState('');
4629
- const [selectedFileId, setSelectedFileId] = useState('');
4629
+ const [internalSelectedFileId, setInternalSelectedFileId] = useState(multiple ? [] : '');
4630
4630
  const [failedImageIds, setFailedImageIds] = useState(new Set());
4631
+ // Use controlled or internal state for selectedFileId
4632
+ const selectedFileId = controlledSelectedFileId ?? internalSelectedFileId;
4633
+ const setSelectedFileId = onSelectedFileIdChange ?? setInternalSelectedFileId;
4631
4634
  const { data: filesData, isLoading, isError, } = useQuery({
4632
4635
  queryKey: ['file-picker-library', searchTerm],
4633
4636
  queryFn: async () => {
@@ -4636,91 +4639,176 @@ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = fa
4636
4639
  const files = await onFetchFiles(searchTerm.trim() || '');
4637
4640
  return { data: files };
4638
4641
  },
4639
- enabled: open && !!onFetchFiles,
4642
+ enabled: enabled && !!onFetchFiles,
4640
4643
  });
4641
4644
  const files = (filesData?.data || []);
4642
4645
  const filteredFiles = filterImageOnly
4643
4646
  ? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
4644
4647
  : files;
4648
+ const handleFileClick = (fileId) => {
4649
+ if (multiple) {
4650
+ const currentSelection = Array.isArray(selectedFileId)
4651
+ ? selectedFileId
4652
+ : [];
4653
+ const newSelection = currentSelection.includes(fileId)
4654
+ ? currentSelection.filter((id) => id !== fileId)
4655
+ : [...currentSelection, fileId];
4656
+ setSelectedFileId(newSelection);
4657
+ if (onFileSelect) {
4658
+ onFileSelect(newSelection);
4659
+ }
4660
+ }
4661
+ else {
4662
+ setSelectedFileId(fileId);
4663
+ if (onFileSelect) {
4664
+ onFileSelect(fileId);
4665
+ }
4666
+ }
4667
+ };
4668
+ const handleImageError = (fileId) => {
4669
+ setFailedImageIds((prev) => new Set(prev).add(fileId));
4670
+ };
4671
+ if (!onFetchFiles)
4672
+ return null;
4673
+ return (jsxs(VStack, { align: "stretch", gap: 4, children: [jsxs(Box, { position: "relative", children: [jsx(Input, { placeholder: labels?.searchPlaceholder ?? 'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
4674
+ borderColor: 'colorPalette.500',
4675
+ _dark: {
4676
+ borderColor: 'colorPalette.400',
4677
+ },
4678
+ boxShadow: {
4679
+ base: '0 0 0 1px var(--chakra-colors-blue-500)',
4680
+ _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
4681
+ },
4682
+ }, pl: 10 }), jsx(Icon, { as: LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxs(Box, { textAlign: "center", py: 8, children: [jsx(Spinner, { size: "lg", colorPalette: "blue" }), jsx(Text, { mt: 4, color: "fg.muted", children: labels?.loading ?? 'Loading files...' })] })), isError && (jsx(Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
4683
+ base: 'colorPalette.200',
4684
+ _dark: 'colorPalette.800',
4685
+ }, colorPalette: "red", borderRadius: "md", p: 4, children: jsx(Text, { color: {
4686
+ base: 'colorPalette.600',
4687
+ _dark: 'colorPalette.300',
4688
+ }, children: labels?.loadingFailed ?? 'Failed to load files' }) })), !isLoading && !isError && (jsx(Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsx(Box, { textAlign: "center", py: 8, children: jsx(Text, { color: "fg.muted", children: labels?.noFilesFound ?? 'No files found' }) })) : (jsx(VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
4689
+ const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
4690
+ const isSelected = multiple
4691
+ ? Array.isArray(selectedFileId) &&
4692
+ selectedFileId.includes(file.id)
4693
+ : selectedFileId === file.id;
4694
+ const imageFailed = failedImageIds.has(file.id);
4695
+ return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
4696
+ ? {
4697
+ base: 'colorPalette.500',
4698
+ _dark: 'colorPalette.400',
4699
+ }
4700
+ : 'border.default', borderRadius: "md", bg: isSelected
4701
+ ? {
4702
+ base: 'colorPalette.50',
4703
+ _dark: 'colorPalette.900/20',
4704
+ }
4705
+ : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file.id), _hover: {
4706
+ borderColor: isSelected
4707
+ ? {
4708
+ base: 'colorPalette.600',
4709
+ _dark: 'colorPalette.400',
4710
+ }
4711
+ : {
4712
+ base: 'colorPalette.300',
4713
+ _dark: 'colorPalette.400',
4714
+ },
4715
+ bg: isSelected
4716
+ ? {
4717
+ base: 'colorPalette.100',
4718
+ _dark: 'colorPalette.800/30',
4719
+ }
4720
+ : 'bg.muted',
4721
+ }, transition: "all 0.2s", children: jsxs(HStack, { gap: 3, children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsx(Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsxs(VStack, { align: "start", flex: 1, gap: 1, children: [jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.name }), jsxs(HStack, { gap: 2, children: [file.size && (jsx(Fragment, { children: jsx(Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
4722
+ ? formatBytes(file.size)
4723
+ : file.size }) })), file.comment && (jsxs(Fragment, { children: [file.size && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsx(Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsx(Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
4724
+ base: 'colorPalette.500',
4725
+ _dark: 'colorPalette.400',
4726
+ }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
4727
+ }) })) }))] }));
4728
+ };
4729
+
4730
+ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, translate, colLabel, }) {
4731
+ const [selectedFileId, setSelectedFileId] = useState('');
4732
+ const [activeTab, setActiveTab] = useState('browse');
4733
+ const [uploadingFiles, setUploadingFiles] = useState(new Set());
4734
+ const [uploadErrors, setUploadErrors] = useState(new Map());
4645
4735
  const handleSelect = () => {
4646
4736
  if (selectedFileId) {
4647
4737
  onSelect(selectedFileId);
4648
4738
  onClose();
4649
4739
  setSelectedFileId('');
4650
- setSearchTerm('');
4740
+ setActiveTab('browse');
4651
4741
  }
4652
4742
  };
4653
4743
  const handleClose = () => {
4654
4744
  onClose();
4655
4745
  setSelectedFileId('');
4656
- setSearchTerm('');
4657
- setFailedImageIds(new Set());
4746
+ setActiveTab('browse');
4747
+ setUploadingFiles(new Set());
4748
+ setUploadErrors(new Map());
4658
4749
  };
4659
- const handleImageError = (fileId) => {
4660
- setFailedImageIds((prev) => new Set(prev).add(fileId));
4750
+ const handleFileUpload = async (files) => {
4751
+ if (!onUploadFile)
4752
+ return;
4753
+ for (const file of files) {
4754
+ const fileKey = `${file.name}-${file.size}`;
4755
+ setUploadingFiles((prev) => new Set(prev).add(fileKey));
4756
+ setUploadErrors((prev) => {
4757
+ const newMap = new Map(prev);
4758
+ newMap.delete(fileKey);
4759
+ return newMap;
4760
+ });
4761
+ try {
4762
+ const fileId = await onUploadFile(file);
4763
+ setSelectedFileId(fileId);
4764
+ setUploadingFiles((prev) => {
4765
+ const newSet = new Set(prev);
4766
+ newSet.delete(fileKey);
4767
+ return newSet;
4768
+ });
4769
+ // Auto-select and close in single-select mode
4770
+ onSelect(fileId);
4771
+ onClose();
4772
+ setSelectedFileId('');
4773
+ setActiveTab('browse');
4774
+ }
4775
+ catch (error) {
4776
+ setUploadingFiles((prev) => {
4777
+ const newSet = new Set(prev);
4778
+ newSet.delete(fileKey);
4779
+ return newSet;
4780
+ });
4781
+ setUploadErrors((prev) => {
4782
+ const newMap = new Map(prev);
4783
+ newMap.set(fileKey, error instanceof Error ? error.message : 'Upload failed');
4784
+ return newMap;
4785
+ });
4786
+ }
4787
+ }
4661
4788
  };
4662
- if (!onFetchFiles)
4789
+ const showTabs = enableUpload && !!onUploadFile && !!onFetchFiles;
4790
+ if (!onFetchFiles && !onUploadFile)
4663
4791
  return null;
4664
- return (jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsx(DialogCloseTrigger, {})] }), jsx(DialogBody, { children: jsxs(VStack, { align: "stretch", gap: 4, children: [jsxs(Box, { position: "relative", children: [jsx(Input, { placeholder: labels?.searchPlaceholder ??
4665
- translate(removeIndex(`${colLabel}.search_placeholder`)) ??
4666
- 'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
4667
- borderColor: 'colorPalette.500',
4668
- _dark: {
4669
- borderColor: 'colorPalette.400',
4670
- },
4671
- boxShadow: {
4672
- base: '0 0 0 1px var(--chakra-colors-blue-500)',
4673
- _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
4674
- },
4675
- }, pl: 10 }), jsx(Icon, { as: LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxs(Box, { textAlign: "center", py: 8, children: [jsx(Spinner, { size: "lg", colorPalette: "blue" }), jsx(Text, { mt: 4, color: "fg.muted", children: labels?.loading ??
4676
- translate(removeIndex(`${colLabel}.loading`)) ??
4677
- 'Loading files...' })] })), isError && (jsx(Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
4678
- base: 'colorPalette.200',
4679
- _dark: 'colorPalette.800',
4680
- }, colorPalette: "red", borderRadius: "md", p: 4, children: jsx(Text, { color: {
4681
- base: 'colorPalette.600',
4682
- _dark: 'colorPalette.300',
4683
- }, children: labels?.loadingFailed ??
4684
- translate(removeIndex(`${colLabel}.error.loading_failed`)) ??
4685
- 'Failed to load files' }) })), !isLoading && !isError && (jsx(Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsx(Box, { textAlign: "center", py: 8, children: jsx(Text, { color: "fg.muted", children: labels?.noFilesFound ??
4686
- translate(removeIndex(`${colLabel}.no_files_found`)) ??
4687
- 'No files found' }) })) : (jsx(VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
4688
- const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
4689
- const isSelected = selectedFileId === file.id;
4690
- const imageFailed = failedImageIds.has(file.id);
4691
- return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
4692
- ? {
4693
- base: 'colorPalette.500',
4694
- _dark: 'colorPalette.400',
4695
- }
4696
- : 'border.default', borderRadius: "md", bg: isSelected
4697
- ? {
4792
+ return (jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsx(DialogCloseTrigger, {})] }), jsx(DialogBody, { children: showTabs ? (jsxs(Tabs.Root, { value: activeTab, onValueChange: (e) => setActiveTab(e.value ?? 'browse'), children: [jsxs(Tabs.List, { children: [jsx(Tabs.Trigger, { value: "browse", children: labels?.browseTab ??
4793
+ translate(removeIndex(`${colLabel}.browse_tab`)) ??
4794
+ 'Browse Library' }), jsx(Tabs.Trigger, { value: "upload", children: labels?.uploadTab ??
4795
+ translate(removeIndex(`${colLabel}.upload_tab`)) ??
4796
+ 'Upload Files' })] }), jsx(Tabs.Content, { value: "browse", children: onFetchFiles && (jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open && activeTab === 'browse', selectedFileId: selectedFileId, onSelectedFileIdChange: setSelectedFileId })) }), jsx(Tabs.Content, { value: "upload", children: jsxs(VStack, { align: "stretch", gap: 4, children: [jsx(FileDropzone, { onDrop: ({ files }) => handleFileUpload(files), placeholder: labels?.fileDropzone ??
4797
+ translate(removeIndex(`${colLabel}.fileDropzone`)) ??
4798
+ 'Drop files here or click to upload' }), uploadingFiles.size > 0 && (jsx(Box, { children: Array.from(uploadingFiles).map((fileKey) => (jsx(Box, { py: 2, children: jsxs(HStack, { gap: 2, children: [jsx(Spinner, { size: "sm", colorPalette: "blue" }), jsxs(Text, { fontSize: "sm", color: "fg.muted", children: [labels?.uploading ??
4799
+ translate(removeIndex(`${colLabel}.uploading`)) ??
4800
+ 'Uploading...', ' ', fileKey.split('-')[0]] })] }) }, fileKey))) })), uploadErrors.size > 0 && (jsx(VStack, { align: "stretch", gap: 2, children: Array.from(uploadErrors.entries()).map(([fileKey, error]) => (jsx(Box, { bg: {
4698
4801
  base: 'colorPalette.50',
4699
4802
  _dark: 'colorPalette.900/20',
4700
- }
4701
- : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => setSelectedFileId(file.id), _hover: {
4702
- borderColor: isSelected
4703
- ? {
4803
+ }, border: "1px solid", borderColor: {
4804
+ base: 'colorPalette.200',
4805
+ _dark: 'colorPalette.800',
4806
+ }, colorPalette: "red", borderRadius: "md", p: 3, children: jsxs(Text, { fontSize: "sm", color: {
4704
4807
  base: 'colorPalette.600',
4705
- _dark: 'colorPalette.400',
4706
- }
4707
- : {
4708
- base: 'colorPalette.300',
4709
- _dark: 'colorPalette.400',
4710
- },
4711
- bg: isSelected
4712
- ? {
4713
- base: 'colorPalette.100',
4714
- _dark: 'colorPalette.800/30',
4715
- }
4716
- : 'bg.muted',
4717
- }, transition: "all 0.2s", children: jsxs(HStack, { gap: 3, children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsx(Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsxs(VStack, { align: "start", flex: 1, gap: 1, children: [jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.name }), jsxs(HStack, { gap: 2, children: [file.size && (jsx(Fragment, { children: jsx(Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
4718
- ? formatBytes(file.size)
4719
- : file.size }) })), file.comment && (jsxs(Fragment, { children: [file.size && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsx(Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsx(Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
4720
- base: 'colorPalette.500',
4721
- _dark: 'colorPalette.400',
4722
- }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
4723
- }) })) }))] }) }), jsx(DialogFooter, { children: jsxs(HStack, { gap: 3, justify: "end", children: [jsx(Button$1, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
4808
+ _dark: 'colorPalette.300',
4809
+ }, children: [fileKey.split('-')[0], ":", ' ', labels?.uploadFailed ??
4810
+ translate(removeIndex(`${colLabel}.upload_failed`)) ??
4811
+ 'Upload failed', error && ` - ${error}`] }) }, fileKey))) }))] }) })] })) : onFetchFiles ? (jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open, selectedFileId: selectedFileId, onSelectedFileIdChange: setSelectedFileId })) : null }), jsx(DialogFooter, { children: jsxs(HStack, { gap: 3, justify: "end", children: [jsx(Button$1, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
4724
4812
  translate(removeIndex(`${colLabel}.cancel`)) ??
4725
4813
  'Cancel' }), jsx(Button$1, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFileId, children: labels?.select ??
4726
4814
  translate(removeIndex(`${colLabel}.select`)) ??
@@ -4730,84 +4818,71 @@ const FilePicker = ({ column, schema, prefix }) => {
4730
4818
  const { setValue, formState: { errors }, watch, } = useFormContext();
4731
4819
  const { filePickerLabels } = useSchemaContext();
4732
4820
  const formI18n = useFormI18n(column, prefix);
4733
- const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, } = schema;
4821
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', type, } = schema;
4734
4822
  const isRequired = required?.some((columnId) => columnId === column);
4735
- const currentValue = watch(column) ?? [];
4736
- const currentFiles = Array.isArray(currentValue)
4737
- ? currentValue
4738
- : [];
4823
+ const isSingleSelect = type === 'string';
4824
+ const currentValue = watch(column) ?? (isSingleSelect ? '' : []);
4825
+ // Handle File objects only
4826
+ const currentFiles = isSingleSelect
4827
+ ? currentValue && currentValue instanceof File
4828
+ ? [currentValue]
4829
+ : []
4830
+ : Array.isArray(currentValue)
4831
+ ? currentValue.filter((f) => f instanceof File)
4832
+ : [];
4739
4833
  const colLabel = formI18n.colLabel;
4740
- const [dialogOpen, setDialogOpen] = useState(false);
4741
4834
  const [failedImageIds, setFailedImageIds] = useState(new Set());
4742
- const { onFetchFiles, enableMediaLibrary = false, filterImageOnly = false, } = filePicker || {};
4743
- const showMediaLibrary = enableMediaLibrary && !!onFetchFiles;
4835
+ // FilePicker variant: Only handle File objects, no media library browser
4744
4836
  const handleImageError = (fileIdentifier) => {
4745
4837
  setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
4746
4838
  };
4747
- const handleMediaLibrarySelect = (fileId) => {
4748
- const newFiles = [...currentFiles, fileId];
4749
- setValue(colLabel, newFiles);
4750
- };
4751
4839
  const handleRemove = (index) => {
4752
- const newFiles = currentFiles.filter((_, i) => i !== index);
4753
- setValue(colLabel, newFiles);
4754
- };
4755
- const isFileObject = (value) => {
4756
- return value instanceof File;
4840
+ if (isSingleSelect) {
4841
+ setValue(colLabel, '');
4842
+ }
4843
+ else {
4844
+ const newFiles = currentFiles.filter((_, i) => i !== index);
4845
+ setValue(colLabel, newFiles);
4846
+ }
4757
4847
  };
4758
4848
  const getFileIdentifier = (file, index) => {
4759
- if (isFileObject(file)) {
4760
- return `${file.name}-${file.size}-${index}`;
4761
- }
4762
- return file;
4849
+ // file-picker: file is a File object, create identifier from name and size
4850
+ return `${file.name}-${file.size}-${index}`;
4763
4851
  };
4764
4852
  const getFileName = (file) => {
4765
- if (isFileObject(file)) {
4766
- return file.name;
4767
- }
4768
- return typeof file === 'string' ? file : 'Unknown file';
4853
+ return file.name;
4769
4854
  };
4770
4855
  const getFileSize = (file) => {
4771
- if (isFileObject(file)) {
4772
- return file.size;
4773
- }
4774
- return undefined;
4856
+ return file.size;
4775
4857
  };
4776
4858
  const isImageFile = (file) => {
4777
- if (isFileObject(file)) {
4778
- return file.type.startsWith('image/');
4779
- }
4780
- if (typeof file === 'string') {
4781
- return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file);
4782
- }
4783
- return false;
4859
+ return file.type.startsWith('image/');
4784
4860
  };
4785
4861
  const getImageUrl = (file) => {
4786
- if (isFileObject(file)) {
4787
- return URL.createObjectURL(file);
4788
- }
4789
- return undefined;
4862
+ return URL.createObjectURL(file);
4790
4863
  };
4791
4864
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4792
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxs(VStack, { align: "stretch", gap: 2, children: [jsx(FileDropzone, { onDrop: ({ files }) => {
4793
- const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => {
4794
- if (isFileObject(cur)) {
4795
- return cur.name === name;
4796
- }
4797
- return false;
4798
- }));
4865
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(FileDropzone, { onDrop: ({ files }) => {
4866
+ // file-picker variant: Store File objects directly (no ID conversion)
4867
+ if (isSingleSelect) {
4868
+ // In single-select mode, use the first file and replace any existing file
4869
+ if (files.length > 0) {
4870
+ setValue(colLabel, files[0]);
4871
+ }
4872
+ }
4873
+ else {
4874
+ // In multi-select mode, filter duplicates and append
4875
+ const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
4799
4876
  setValue(colLabel, [...currentFiles, ...newFiles]);
4800
- }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }), showMediaLibrary && (jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
4801
- formI18n.t('browse_library') ??
4802
- 'Browse from Library' }))] }), showMediaLibrary && (jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
4803
- formI18n.t('dialog_title') ??
4804
- 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel })), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
4877
+ }
4878
+ }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }) }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
4805
4879
  const fileIdentifier = getFileIdentifier(file, index);
4806
4880
  const fileName = getFileName(file);
4807
4881
  const fileSize = getFileSize(file);
4808
4882
  const isImage = isImageFile(file);
4809
4883
  const imageUrl = getImageUrl(file);
4810
4884
  const imageFailed = failedImageIds.has(fileIdentifier);
4885
+ // File Viewer
4811
4886
  return (jsx(Card.Root, { variant: 'subtle', colorPalette: "blue", children: jsxs(Card.Body, { gap: "2", cursor: 'pointer', onClick: () => handleRemove(index), display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', border: "2px solid", borderColor: "border.default", borderRadius: "md", _hover: {
4812
4887
  borderColor: 'colorPalette.300',
4813
4888
  bg: 'bg.muted',
@@ -4815,6 +4890,66 @@ const FilePicker = ({ column, schema, prefix }) => {
4815
4890
  }) })] }));
4816
4891
  };
4817
4892
 
4893
+ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
4894
+ const { setValue, formState: { errors }, watch, } = useFormContext();
4895
+ const { filePickerLabels } = useSchemaContext();
4896
+ const formI18n = useFormI18n(column, prefix);
4897
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
4898
+ const isRequired = required?.some((columnId) => columnId === column);
4899
+ const isSingleSelect = type === 'string';
4900
+ const currentValue = watch(column) ?? (isSingleSelect ? '' : []);
4901
+ // Handle string IDs only
4902
+ const currentFileIds = isSingleSelect
4903
+ ? currentValue
4904
+ ? [currentValue]
4905
+ : []
4906
+ : Array.isArray(currentValue)
4907
+ ? currentValue
4908
+ : [];
4909
+ const colLabel = formI18n.colLabel;
4910
+ const [dialogOpen, setDialogOpen] = useState(false);
4911
+ const [failedImageIds, setFailedImageIds] = useState(new Set());
4912
+ const { onFetchFiles, filterImageOnly = false, enableUpload = false, onUploadFile, } = filePicker || {};
4913
+ if (!onFetchFiles) {
4914
+ return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4915
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(Text, { color: "fg.muted", children: "Media library browser requires onFetchFiles" }) }));
4916
+ }
4917
+ const handleMediaLibrarySelect = (fileId) => {
4918
+ if (isSingleSelect) {
4919
+ setValue(colLabel, fileId);
4920
+ }
4921
+ else {
4922
+ const newFileIds = [...currentFileIds, fileId];
4923
+ setValue(colLabel, newFileIds);
4924
+ }
4925
+ };
4926
+ const handleRemove = (index) => {
4927
+ if (isSingleSelect) {
4928
+ setValue(colLabel, '');
4929
+ }
4930
+ else {
4931
+ const newFileIds = currentFileIds.filter((_, i) => i !== index);
4932
+ setValue(colLabel, newFileIds);
4933
+ }
4934
+ };
4935
+ const isImageId = (fileId) => {
4936
+ return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
4937
+ };
4938
+ return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4939
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
4940
+ formI18n.t('browse_library') ??
4941
+ 'Browse from Library' }) }), jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
4942
+ formI18n.t('dialog_title') ??
4943
+ 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
4944
+ const isImage = isImageId(fileId);
4945
+ const imageFailed = failedImageIds.has(fileId);
4946
+ return (jsx(Card.Root, { variant: 'subtle', colorPalette: "blue", children: jsxs(Card.Body, { gap: "2", cursor: 'pointer', onClick: () => handleRemove(index), display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', border: "2px solid", borderColor: "border.default", borderRadius: "md", _hover: {
4947
+ borderColor: 'colorPalette.300',
4948
+ bg: 'bg.muted',
4949
+ }, transition: "all 0.2s", children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, marginRight: "2", children: isImage && !imageFailed ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsx(VStack, { align: "start", flex: 1, gap: 1, children: jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: fileId }) }), jsx(Icon, { as: TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, `${fileId}-${index}`));
4950
+ }) })] }));
4951
+ };
4952
+
4818
4953
  const ToggleTip = React.forwardRef(function ToggleTip(props, ref) {
4819
4954
  const { showArrow, children, portalled = true, content, portalRef, ...rest } = props;
4820
4955
  return (jsxs(Popover.Root, { ...rest, positioning: { ...rest.positioning, gutter: 4 }, children: [jsx(Popover.Trigger, { asChild: true, children: children }), jsx(Portal, { disabled: !portalled, container: portalRef, children: jsx(Popover.Positioner, { children: jsxs(Popover.Content, { width: "auto", px: "2", py: "1", textStyle: "xs", rounded: "sm", ref: ref, children: [showArrow && (jsx(Popover.Arrow, { children: jsx(Popover.ArrowTip, {}) })), content] }) }) })] }));
@@ -5980,6 +6115,9 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
5980
6115
  if (variant === 'text-area') {
5981
6116
  return jsx(TextAreaInput, { schema: colSchema, prefix, column });
5982
6117
  }
6118
+ if (variant === 'media-library-browser') {
6119
+ return (jsx(FormMediaLibraryBrowser, { schema: colSchema, prefix, column }));
6120
+ }
5983
6121
  return jsx(StringInputField, { schema: colSchema, prefix, column });
5984
6122
  }
5985
6123
  if (type === 'number' || type === 'integer') {
@@ -6005,6 +6143,9 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
6005
6143
  if (variant === 'file-picker') {
6006
6144
  return jsx(FilePicker, { schema: colSchema, prefix, column });
6007
6145
  }
6146
+ if (variant === 'media-library-browser') {
6147
+ return (jsx(FormMediaLibraryBrowser, { schema: colSchema, prefix, column }));
6148
+ }
6008
6149
  if (variant === 'date-range') {
6009
6150
  return jsx(DateRangePicker, { schema: colSchema, prefix, column });
6010
6151
  }
@@ -6396,59 +6537,62 @@ const DateTimeViewer = ({ column, schema, prefix }) => {
6396
6537
  const SchemaViewer = ({ schema, prefix, column, }) => {
6397
6538
  const colSchema = schema;
6398
6539
  const { type, variant, properties: innerProperties, foreign_key, items, format, } = schema;
6399
- if (variant === "custom-input") {
6540
+ if (variant === 'custom-input') {
6400
6541
  return jsx(CustomViewer, { schema: colSchema, prefix, column });
6401
6542
  }
6402
- if (type === "string") {
6543
+ if (type === 'string') {
6403
6544
  if ((schema.enum ?? []).length > 0) {
6404
6545
  return jsx(EnumViewer, { schema: colSchema, prefix, column });
6405
6546
  }
6406
- if (variant === "id-picker") {
6547
+ if (variant === 'id-picker') {
6407
6548
  idPickerSanityCheck(column, foreign_key);
6408
6549
  return jsx(IdViewer, { schema: colSchema, prefix, column });
6409
6550
  }
6410
- if (format === "time") {
6551
+ if (format === 'time') {
6411
6552
  return jsx(TimeViewer, { schema: colSchema, prefix, column });
6412
6553
  }
6413
- if (format === "date") {
6554
+ if (format === 'date') {
6414
6555
  return jsx(DateViewer, { schema: colSchema, prefix, column });
6415
6556
  }
6416
- if (format === "date-time") {
6557
+ if (format === 'date-time') {
6417
6558
  return jsx(DateTimeViewer, { schema: colSchema, prefix, column });
6418
6559
  }
6419
- if (variant === "text-area") {
6560
+ if (variant === 'text-area') {
6420
6561
  return jsx(TextAreaViewer, { schema: colSchema, prefix, column });
6421
6562
  }
6422
6563
  return jsx(StringViewer, { schema: colSchema, prefix, column });
6423
6564
  }
6424
- if (type === "number" || type === "integer") {
6565
+ if (type === 'number' || type === 'integer') {
6425
6566
  return jsx(NumberViewer, { schema: colSchema, prefix, column });
6426
6567
  }
6427
- if (type === "boolean") {
6568
+ if (type === 'boolean') {
6428
6569
  return jsx(BooleanViewer, { schema: colSchema, prefix, column });
6429
6570
  }
6430
- if (type === "object") {
6571
+ if (type === 'object') {
6431
6572
  if (innerProperties) {
6432
6573
  return jsx(ObjectViewer, { schema: colSchema, prefix, column });
6433
6574
  }
6434
6575
  return jsx(RecordInput, { schema: colSchema, prefix, column });
6435
6576
  }
6436
- if (type === "array") {
6437
- if (variant === "id-picker") {
6577
+ if (type === 'array') {
6578
+ if (variant === 'id-picker') {
6438
6579
  idPickerSanityCheck(column, foreign_key);
6439
6580
  return (jsx(IdViewer, { schema: colSchema, prefix, column, isMultiple: true }));
6440
6581
  }
6441
- if (variant === "tag-picker") {
6582
+ if (variant === 'tag-picker') {
6442
6583
  return jsx(TagViewer, { schema: colSchema, prefix, column });
6443
6584
  }
6444
- if (variant === "file-picker") {
6585
+ if (variant === 'file-picker') {
6586
+ return jsx(FileViewer, { schema: colSchema, prefix, column });
6587
+ }
6588
+ if (variant === 'media-library-browser') {
6445
6589
  return jsx(FileViewer, { schema: colSchema, prefix, column });
6446
6590
  }
6447
- if (variant === "enum-picker") {
6591
+ if (variant === 'enum-picker') {
6448
6592
  const { items } = schema;
6449
6593
  const { enum: enumItems } = items;
6450
6594
  const enumSchema = {
6451
- type: "string",
6595
+ type: 'string',
6452
6596
  enum: enumItems,
6453
6597
  };
6454
6598
  return (jsx(EnumViewer, { isMultiple: true, schema: enumSchema, prefix, column }));
@@ -6458,7 +6602,7 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
6458
6602
  }
6459
6603
  return jsx(Text, { children: `array ${column}` });
6460
6604
  }
6461
- if (type === "null") {
6605
+ if (type === 'null') {
6462
6606
  return jsx(Text, { children: `null ${column}` });
6463
6607
  }
6464
6608
  return jsx(Text, { children: "missing type" });
@@ -7372,4 +7516,4 @@ function DataTableServer({ columns, enableRowSelection = true, enableMultiRowSel
7372
7516
  }, children: jsx(DataTableServerContext.Provider, { value: { url, query }, children: children }) }));
7373
7517
  }
7374
7518
 
7375
- export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
7519
+ export { CardHeader, DataDisplay, DataTable, DataTableServer, DefaultCardTitle, DefaultForm, DefaultTable, DefaultTableServer, DensityToggleButton, EditSortingButton, EmptyState, ErrorAlert, FilterDialog, FormBody, FormRoot, FormTitle, GlobalFilter, MediaLibraryBrowser, PageSizeControl, Pagination, RecordDisplay, ReloadButton, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, RowCountText, Table, TableBody, TableCardContainer, TableCards, TableComponent, TableControls, TableDataDisplay, TableFilter, TableFilterTags, TableFooter, TableHeader, TableLoadingComponent, TableSelector, TableSorter, TableViewer, TextCell, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
@@ -0,0 +1,22 @@
1
+ import { FilePickerMediaFile, FilePickerLabels } from './types/CustomJSONSchema7';
2
+ type MediaLibraryBrowserPropsBase = {
3
+ onFetchFiles?: (search: string) => Promise<FilePickerMediaFile[]>;
4
+ filterImageOnly?: boolean;
5
+ labels?: FilePickerLabels;
6
+ enabled?: boolean;
7
+ };
8
+ type MediaLibraryBrowserPropsSingle = MediaLibraryBrowserPropsBase & {
9
+ multiple?: false;
10
+ onFileSelect?: (fileId: string) => void;
11
+ selectedFileId?: string;
12
+ onSelectedFileIdChange?: (fileId: string) => void;
13
+ };
14
+ type MediaLibraryBrowserPropsMultiple = MediaLibraryBrowserPropsBase & {
15
+ multiple: true;
16
+ onFileSelect?: (fileId: string[]) => void;
17
+ selectedFileId?: string[];
18
+ onSelectedFileIdChange?: (fileId: string[]) => void;
19
+ };
20
+ export type MediaLibraryBrowserProps = MediaLibraryBrowserPropsSingle | MediaLibraryBrowserPropsMultiple;
21
+ export declare const MediaLibraryBrowser: ({ onFetchFiles, filterImageOnly, labels, enabled, multiple, onFileSelect, selectedFileId: controlledSelectedFileId, onSelectedFileIdChange, }: MediaLibraryBrowserProps) => import("react/jsx-runtime").JSX.Element | null;
22
+ export {};