@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.js CHANGED
@@ -4644,10 +4644,13 @@ function formatBytes(bytes) {
4644
4644
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
4645
4645
  }
4646
4646
 
4647
- function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, labels, translate, colLabel, }) {
4647
+ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect, selectedFileId: controlledSelectedFileId, onSelectedFileIdChange, }) => {
4648
4648
  const [searchTerm, setSearchTerm] = React.useState('');
4649
- const [selectedFileId, setSelectedFileId] = React.useState('');
4649
+ const [internalSelectedFileId, setInternalSelectedFileId] = React.useState(multiple ? [] : '');
4650
4650
  const [failedImageIds, setFailedImageIds] = React.useState(new Set());
4651
+ // Use controlled or internal state for selectedFileId
4652
+ const selectedFileId = controlledSelectedFileId ?? internalSelectedFileId;
4653
+ const setSelectedFileId = onSelectedFileIdChange ?? setInternalSelectedFileId;
4651
4654
  const { data: filesData, isLoading, isError, } = reactQuery.useQuery({
4652
4655
  queryKey: ['file-picker-library', searchTerm],
4653
4656
  queryFn: async () => {
@@ -4656,91 +4659,176 @@ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = fa
4656
4659
  const files = await onFetchFiles(searchTerm.trim() || '');
4657
4660
  return { data: files };
4658
4661
  },
4659
- enabled: open && !!onFetchFiles,
4662
+ enabled: enabled && !!onFetchFiles,
4660
4663
  });
4661
4664
  const files = (filesData?.data || []);
4662
4665
  const filteredFiles = filterImageOnly
4663
4666
  ? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
4664
4667
  : files;
4668
+ const handleFileClick = (fileId) => {
4669
+ if (multiple) {
4670
+ const currentSelection = Array.isArray(selectedFileId)
4671
+ ? selectedFileId
4672
+ : [];
4673
+ const newSelection = currentSelection.includes(fileId)
4674
+ ? currentSelection.filter((id) => id !== fileId)
4675
+ : [...currentSelection, fileId];
4676
+ setSelectedFileId(newSelection);
4677
+ if (onFileSelect) {
4678
+ onFileSelect(newSelection);
4679
+ }
4680
+ }
4681
+ else {
4682
+ setSelectedFileId(fileId);
4683
+ if (onFileSelect) {
4684
+ onFileSelect(fileId);
4685
+ }
4686
+ }
4687
+ };
4688
+ const handleImageError = (fileId) => {
4689
+ setFailedImageIds((prev) => new Set(prev).add(fileId));
4690
+ };
4691
+ if (!onFetchFiles)
4692
+ return null;
4693
+ return (jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 4, children: [jsxRuntime.jsxs(react.Box, { position: "relative", children: [jsxRuntime.jsx(react.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: {
4694
+ borderColor: 'colorPalette.500',
4695
+ _dark: {
4696
+ borderColor: 'colorPalette.400',
4697
+ },
4698
+ boxShadow: {
4699
+ base: '0 0 0 1px var(--chakra-colors-blue-500)',
4700
+ _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
4701
+ },
4702
+ }, pl: 10 }), jsxRuntime.jsx(react.Icon, { as: lu.LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxRuntime.jsxs(react.Box, { textAlign: "center", py: 8, children: [jsxRuntime.jsx(react.Spinner, { size: "lg", colorPalette: "blue" }), jsxRuntime.jsx(react.Text, { mt: 4, color: "fg.muted", children: labels?.loading ?? 'Loading files...' })] })), isError && (jsxRuntime.jsx(react.Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
4703
+ base: 'colorPalette.200',
4704
+ _dark: 'colorPalette.800',
4705
+ }, colorPalette: "red", borderRadius: "md", p: 4, children: jsxRuntime.jsx(react.Text, { color: {
4706
+ base: 'colorPalette.600',
4707
+ _dark: 'colorPalette.300',
4708
+ }, children: labels?.loadingFailed ?? 'Failed to load files' }) })), !isLoading && !isError && (jsxRuntime.jsx(react.Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsxRuntime.jsx(react.Box, { textAlign: "center", py: 8, children: jsxRuntime.jsx(react.Text, { color: "fg.muted", children: labels?.noFilesFound ?? 'No files found' }) })) : (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
4709
+ const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
4710
+ const isSelected = multiple
4711
+ ? Array.isArray(selectedFileId) &&
4712
+ selectedFileId.includes(file.id)
4713
+ : selectedFileId === file.id;
4714
+ const imageFailed = failedImageIds.has(file.id);
4715
+ return (jsxRuntime.jsx(react.Box, { p: 3, border: "2px solid", borderColor: isSelected
4716
+ ? {
4717
+ base: 'colorPalette.500',
4718
+ _dark: 'colorPalette.400',
4719
+ }
4720
+ : 'border.default', borderRadius: "md", bg: isSelected
4721
+ ? {
4722
+ base: 'colorPalette.50',
4723
+ _dark: 'colorPalette.900/20',
4724
+ }
4725
+ : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file.id), _hover: {
4726
+ borderColor: isSelected
4727
+ ? {
4728
+ base: 'colorPalette.600',
4729
+ _dark: 'colorPalette.400',
4730
+ }
4731
+ : {
4732
+ base: 'colorPalette.300',
4733
+ _dark: 'colorPalette.400',
4734
+ },
4735
+ bg: isSelected
4736
+ ? {
4737
+ base: 'colorPalette.100',
4738
+ _dark: 'colorPalette.800/30',
4739
+ }
4740
+ : 'bg.muted',
4741
+ }, transition: "all 0.2s", children: jsxRuntime.jsxs(react.HStack, { gap: 3, children: [jsxRuntime.jsx(react.Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsxRuntime.jsx(react.Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 6, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 6, color: "fg.muted" })) }), jsxRuntime.jsxs(react.VStack, { align: "start", flex: 1, gap: 1, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.name }), jsxRuntime.jsxs(react.HStack, { gap: 2, children: [file.size && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
4742
+ ? formatBytes(file.size)
4743
+ : file.size }) })), file.comment && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [file.size && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsxRuntime.jsx(react.Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
4744
+ base: 'colorPalette.500',
4745
+ _dark: 'colorPalette.400',
4746
+ }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsxRuntime.jsx(react.Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
4747
+ }) })) }))] }));
4748
+ };
4749
+
4750
+ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, translate, colLabel, }) {
4751
+ const [selectedFileId, setSelectedFileId] = React.useState('');
4752
+ const [activeTab, setActiveTab] = React.useState('browse');
4753
+ const [uploadingFiles, setUploadingFiles] = React.useState(new Set());
4754
+ const [uploadErrors, setUploadErrors] = React.useState(new Map());
4665
4755
  const handleSelect = () => {
4666
4756
  if (selectedFileId) {
4667
4757
  onSelect(selectedFileId);
4668
4758
  onClose();
4669
4759
  setSelectedFileId('');
4670
- setSearchTerm('');
4760
+ setActiveTab('browse');
4671
4761
  }
4672
4762
  };
4673
4763
  const handleClose = () => {
4674
4764
  onClose();
4675
4765
  setSelectedFileId('');
4676
- setSearchTerm('');
4677
- setFailedImageIds(new Set());
4766
+ setActiveTab('browse');
4767
+ setUploadingFiles(new Set());
4768
+ setUploadErrors(new Map());
4678
4769
  };
4679
- const handleImageError = (fileId) => {
4680
- setFailedImageIds((prev) => new Set(prev).add(fileId));
4770
+ const handleFileUpload = async (files) => {
4771
+ if (!onUploadFile)
4772
+ return;
4773
+ for (const file of files) {
4774
+ const fileKey = `${file.name}-${file.size}`;
4775
+ setUploadingFiles((prev) => new Set(prev).add(fileKey));
4776
+ setUploadErrors((prev) => {
4777
+ const newMap = new Map(prev);
4778
+ newMap.delete(fileKey);
4779
+ return newMap;
4780
+ });
4781
+ try {
4782
+ const fileId = await onUploadFile(file);
4783
+ setSelectedFileId(fileId);
4784
+ setUploadingFiles((prev) => {
4785
+ const newSet = new Set(prev);
4786
+ newSet.delete(fileKey);
4787
+ return newSet;
4788
+ });
4789
+ // Auto-select and close in single-select mode
4790
+ onSelect(fileId);
4791
+ onClose();
4792
+ setSelectedFileId('');
4793
+ setActiveTab('browse');
4794
+ }
4795
+ catch (error) {
4796
+ setUploadingFiles((prev) => {
4797
+ const newSet = new Set(prev);
4798
+ newSet.delete(fileKey);
4799
+ return newSet;
4800
+ });
4801
+ setUploadErrors((prev) => {
4802
+ const newMap = new Map(prev);
4803
+ newMap.set(fileKey, error instanceof Error ? error.message : 'Upload failed');
4804
+ return newMap;
4805
+ });
4806
+ }
4807
+ }
4681
4808
  };
4682
- if (!onFetchFiles)
4809
+ const showTabs = enableUpload && !!onUploadFile && !!onFetchFiles;
4810
+ if (!onFetchFiles && !onUploadFile)
4683
4811
  return null;
4684
- return (jsxRuntime.jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxRuntime.jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxRuntime.jsxs(DialogHeader, { children: [jsxRuntime.jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsxRuntime.jsx(DialogCloseTrigger, {})] }), jsxRuntime.jsx(DialogBody, { children: jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 4, children: [jsxRuntime.jsxs(react.Box, { position: "relative", children: [jsxRuntime.jsx(react.Input, { placeholder: labels?.searchPlaceholder ??
4685
- translate(removeIndex(`${colLabel}.search_placeholder`)) ??
4686
- 'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
4687
- borderColor: 'colorPalette.500',
4688
- _dark: {
4689
- borderColor: 'colorPalette.400',
4690
- },
4691
- boxShadow: {
4692
- base: '0 0 0 1px var(--chakra-colors-blue-500)',
4693
- _dark: '0 0 0 1px var(--chakra-colors-blue-400)',
4694
- },
4695
- }, pl: 10 }), jsxRuntime.jsx(react.Icon, { as: lu.LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxRuntime.jsxs(react.Box, { textAlign: "center", py: 8, children: [jsxRuntime.jsx(react.Spinner, { size: "lg", colorPalette: "blue" }), jsxRuntime.jsx(react.Text, { mt: 4, color: "fg.muted", children: labels?.loading ??
4696
- translate(removeIndex(`${colLabel}.loading`)) ??
4697
- 'Loading files...' })] })), isError && (jsxRuntime.jsx(react.Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
4698
- base: 'colorPalette.200',
4699
- _dark: 'colorPalette.800',
4700
- }, colorPalette: "red", borderRadius: "md", p: 4, children: jsxRuntime.jsx(react.Text, { color: {
4701
- base: 'colorPalette.600',
4702
- _dark: 'colorPalette.300',
4703
- }, children: labels?.loadingFailed ??
4704
- translate(removeIndex(`${colLabel}.error.loading_failed`)) ??
4705
- 'Failed to load files' }) })), !isLoading && !isError && (jsxRuntime.jsx(react.Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsxRuntime.jsx(react.Box, { textAlign: "center", py: 8, children: jsxRuntime.jsx(react.Text, { color: "fg.muted", children: labels?.noFilesFound ??
4706
- translate(removeIndex(`${colLabel}.no_files_found`)) ??
4707
- 'No files found' }) })) : (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
4708
- const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
4709
- const isSelected = selectedFileId === file.id;
4710
- const imageFailed = failedImageIds.has(file.id);
4711
- return (jsxRuntime.jsx(react.Box, { p: 3, border: "2px solid", borderColor: isSelected
4712
- ? {
4713
- base: 'colorPalette.500',
4714
- _dark: 'colorPalette.400',
4715
- }
4716
- : 'border.default', borderRadius: "md", bg: isSelected
4717
- ? {
4812
+ return (jsxRuntime.jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxRuntime.jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxRuntime.jsxs(DialogHeader, { children: [jsxRuntime.jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsxRuntime.jsx(DialogCloseTrigger, {})] }), jsxRuntime.jsx(DialogBody, { children: showTabs ? (jsxRuntime.jsxs(react.Tabs.Root, { value: activeTab, onValueChange: (e) => setActiveTab(e.value ?? 'browse'), children: [jsxRuntime.jsxs(react.Tabs.List, { children: [jsxRuntime.jsx(react.Tabs.Trigger, { value: "browse", children: labels?.browseTab ??
4813
+ translate(removeIndex(`${colLabel}.browse_tab`)) ??
4814
+ 'Browse Library' }), jsxRuntime.jsx(react.Tabs.Trigger, { value: "upload", children: labels?.uploadTab ??
4815
+ translate(removeIndex(`${colLabel}.upload_tab`)) ??
4816
+ 'Upload Files' })] }), jsxRuntime.jsx(react.Tabs.Content, { value: "browse", children: onFetchFiles && (jsxRuntime.jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open && activeTab === 'browse', selectedFileId: selectedFileId, onSelectedFileIdChange: setSelectedFileId })) }), jsxRuntime.jsx(react.Tabs.Content, { value: "upload", children: jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 4, children: [jsxRuntime.jsx(FileDropzone, { onDrop: ({ files }) => handleFileUpload(files), placeholder: labels?.fileDropzone ??
4817
+ translate(removeIndex(`${colLabel}.fileDropzone`)) ??
4818
+ 'Drop files here or click to upload' }), uploadingFiles.size > 0 && (jsxRuntime.jsx(react.Box, { children: Array.from(uploadingFiles).map((fileKey) => (jsxRuntime.jsx(react.Box, { py: 2, children: jsxRuntime.jsxs(react.HStack, { gap: 2, children: [jsxRuntime.jsx(react.Spinner, { size: "sm", colorPalette: "blue" }), jsxRuntime.jsxs(react.Text, { fontSize: "sm", color: "fg.muted", children: [labels?.uploading ??
4819
+ translate(removeIndex(`${colLabel}.uploading`)) ??
4820
+ 'Uploading...', ' ', fileKey.split('-')[0]] })] }) }, fileKey))) })), uploadErrors.size > 0 && (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: Array.from(uploadErrors.entries()).map(([fileKey, error]) => (jsxRuntime.jsx(react.Box, { bg: {
4718
4821
  base: 'colorPalette.50',
4719
4822
  _dark: 'colorPalette.900/20',
4720
- }
4721
- : 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => setSelectedFileId(file.id), _hover: {
4722
- borderColor: isSelected
4723
- ? {
4823
+ }, border: "1px solid", borderColor: {
4824
+ base: 'colorPalette.200',
4825
+ _dark: 'colorPalette.800',
4826
+ }, colorPalette: "red", borderRadius: "md", p: 3, children: jsxRuntime.jsxs(react.Text, { fontSize: "sm", color: {
4724
4827
  base: 'colorPalette.600',
4725
- _dark: 'colorPalette.400',
4726
- }
4727
- : {
4728
- base: 'colorPalette.300',
4729
- _dark: 'colorPalette.400',
4730
- },
4731
- bg: isSelected
4732
- ? {
4733
- base: 'colorPalette.100',
4734
- _dark: 'colorPalette.800/30',
4735
- }
4736
- : 'bg.muted',
4737
- }, transition: "all 0.2s", children: jsxRuntime.jsxs(react.HStack, { gap: 3, children: [jsxRuntime.jsx(react.Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsxRuntime.jsx(react.Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 6, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 6, color: "fg.muted" })) }), jsxRuntime.jsxs(react.VStack, { align: "start", flex: 1, gap: 1, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.name }), jsxRuntime.jsxs(react.HStack, { gap: 2, children: [file.size && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
4738
- ? formatBytes(file.size)
4739
- : file.size }) })), file.comment && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [file.size && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsxRuntime.jsx(react.Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
4740
- base: 'colorPalette.500',
4741
- _dark: 'colorPalette.400',
4742
- }, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsxRuntime.jsx(react.Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
4743
- }) })) }))] }) }), jsxRuntime.jsx(DialogFooter, { children: jsxRuntime.jsxs(react.HStack, { gap: 3, justify: "end", children: [jsxRuntime.jsx(react.Button, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
4828
+ _dark: 'colorPalette.300',
4829
+ }, children: [fileKey.split('-')[0], ":", ' ', labels?.uploadFailed ??
4830
+ translate(removeIndex(`${colLabel}.upload_failed`)) ??
4831
+ 'Upload failed', error && ` - ${error}`] }) }, fileKey))) }))] }) })] })) : onFetchFiles ? (jsxRuntime.jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open, selectedFileId: selectedFileId, onSelectedFileIdChange: setSelectedFileId })) : null }), jsxRuntime.jsx(DialogFooter, { children: jsxRuntime.jsxs(react.HStack, { gap: 3, justify: "end", children: [jsxRuntime.jsx(react.Button, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
4744
4832
  translate(removeIndex(`${colLabel}.cancel`)) ??
4745
4833
  'Cancel' }), jsxRuntime.jsx(react.Button, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFileId, children: labels?.select ??
4746
4834
  translate(removeIndex(`${colLabel}.select`)) ??
@@ -4750,84 +4838,71 @@ const FilePicker = ({ column, schema, prefix }) => {
4750
4838
  const { setValue, formState: { errors }, watch, } = reactHookForm.useFormContext();
4751
4839
  const { filePickerLabels } = useSchemaContext();
4752
4840
  const formI18n = useFormI18n(column, prefix);
4753
- const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, } = schema;
4841
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', type, } = schema;
4754
4842
  const isRequired = required?.some((columnId) => columnId === column);
4755
- const currentValue = watch(column) ?? [];
4756
- const currentFiles = Array.isArray(currentValue)
4757
- ? currentValue
4758
- : [];
4843
+ const isSingleSelect = type === 'string';
4844
+ const currentValue = watch(column) ?? (isSingleSelect ? '' : []);
4845
+ // Handle File objects only
4846
+ const currentFiles = isSingleSelect
4847
+ ? currentValue && currentValue instanceof File
4848
+ ? [currentValue]
4849
+ : []
4850
+ : Array.isArray(currentValue)
4851
+ ? currentValue.filter((f) => f instanceof File)
4852
+ : [];
4759
4853
  const colLabel = formI18n.colLabel;
4760
- const [dialogOpen, setDialogOpen] = React.useState(false);
4761
4854
  const [failedImageIds, setFailedImageIds] = React.useState(new Set());
4762
- const { onFetchFiles, enableMediaLibrary = false, filterImageOnly = false, } = filePicker || {};
4763
- const showMediaLibrary = enableMediaLibrary && !!onFetchFiles;
4855
+ // FilePicker variant: Only handle File objects, no media library browser
4764
4856
  const handleImageError = (fileIdentifier) => {
4765
4857
  setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
4766
4858
  };
4767
- const handleMediaLibrarySelect = (fileId) => {
4768
- const newFiles = [...currentFiles, fileId];
4769
- setValue(colLabel, newFiles);
4770
- };
4771
4859
  const handleRemove = (index) => {
4772
- const newFiles = currentFiles.filter((_, i) => i !== index);
4773
- setValue(colLabel, newFiles);
4774
- };
4775
- const isFileObject = (value) => {
4776
- return value instanceof File;
4860
+ if (isSingleSelect) {
4861
+ setValue(colLabel, '');
4862
+ }
4863
+ else {
4864
+ const newFiles = currentFiles.filter((_, i) => i !== index);
4865
+ setValue(colLabel, newFiles);
4866
+ }
4777
4867
  };
4778
4868
  const getFileIdentifier = (file, index) => {
4779
- if (isFileObject(file)) {
4780
- return `${file.name}-${file.size}-${index}`;
4781
- }
4782
- return file;
4869
+ // file-picker: file is a File object, create identifier from name and size
4870
+ return `${file.name}-${file.size}-${index}`;
4783
4871
  };
4784
4872
  const getFileName = (file) => {
4785
- if (isFileObject(file)) {
4786
- return file.name;
4787
- }
4788
- return typeof file === 'string' ? file : 'Unknown file';
4873
+ return file.name;
4789
4874
  };
4790
4875
  const getFileSize = (file) => {
4791
- if (isFileObject(file)) {
4792
- return file.size;
4793
- }
4794
- return undefined;
4876
+ return file.size;
4795
4877
  };
4796
4878
  const isImageFile = (file) => {
4797
- if (isFileObject(file)) {
4798
- return file.type.startsWith('image/');
4799
- }
4800
- if (typeof file === 'string') {
4801
- return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file);
4802
- }
4803
- return false;
4879
+ return file.type.startsWith('image/');
4804
4880
  };
4805
4881
  const getImageUrl = (file) => {
4806
- if (isFileObject(file)) {
4807
- return URL.createObjectURL(file);
4808
- }
4809
- return undefined;
4882
+ return URL.createObjectURL(file);
4810
4883
  };
4811
4884
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4812
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 2, children: [jsxRuntime.jsx(FileDropzone, { onDrop: ({ files }) => {
4813
- const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => {
4814
- if (isFileObject(cur)) {
4815
- return cur.name === name;
4816
- }
4817
- return false;
4818
- }));
4885
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: jsxRuntime.jsx(FileDropzone, { onDrop: ({ files }) => {
4886
+ // file-picker variant: Store File objects directly (no ID conversion)
4887
+ if (isSingleSelect) {
4888
+ // In single-select mode, use the first file and replace any existing file
4889
+ if (files.length > 0) {
4890
+ setValue(colLabel, files[0]);
4891
+ }
4892
+ }
4893
+ else {
4894
+ // In multi-select mode, filter duplicates and append
4895
+ const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => cur.name === name));
4819
4896
  setValue(colLabel, [...currentFiles, ...newFiles]);
4820
- }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }), showMediaLibrary && (jsxRuntime.jsx(react.Button, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
4821
- formI18n.t('browse_library') ??
4822
- 'Browse from Library' }))] }), showMediaLibrary && (jsxRuntime.jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
4823
- formI18n.t('dialog_title') ??
4824
- 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel })), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
4897
+ }
4898
+ }, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }) }), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
4825
4899
  const fileIdentifier = getFileIdentifier(file, index);
4826
4900
  const fileName = getFileName(file);
4827
4901
  const fileSize = getFileSize(file);
4828
4902
  const isImage = isImageFile(file);
4829
4903
  const imageUrl = getImageUrl(file);
4830
4904
  const imageFailed = failedImageIds.has(fileIdentifier);
4905
+ // File Viewer
4831
4906
  return (jsxRuntime.jsx(react.Card.Root, { variant: 'subtle', colorPalette: "blue", children: jsxRuntime.jsxs(react.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: {
4832
4907
  borderColor: 'colorPalette.300',
4833
4908
  bg: 'bg.muted',
@@ -4835,6 +4910,66 @@ const FilePicker = ({ column, schema, prefix }) => {
4835
4910
  }) })] }));
4836
4911
  };
4837
4912
 
4913
+ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
4914
+ const { setValue, formState: { errors }, watch, } = reactHookForm.useFormContext();
4915
+ const { filePickerLabels } = useSchemaContext();
4916
+ const formI18n = useFormI18n(column, prefix);
4917
+ const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, type, } = schema;
4918
+ const isRequired = required?.some((columnId) => columnId === column);
4919
+ const isSingleSelect = type === 'string';
4920
+ const currentValue = watch(column) ?? (isSingleSelect ? '' : []);
4921
+ // Handle string IDs only
4922
+ const currentFileIds = isSingleSelect
4923
+ ? currentValue
4924
+ ? [currentValue]
4925
+ : []
4926
+ : Array.isArray(currentValue)
4927
+ ? currentValue
4928
+ : [];
4929
+ const colLabel = formI18n.colLabel;
4930
+ const [dialogOpen, setDialogOpen] = React.useState(false);
4931
+ const [failedImageIds, setFailedImageIds] = React.useState(new Set());
4932
+ const { onFetchFiles, filterImageOnly = false, enableUpload = false, onUploadFile, } = filePicker || {};
4933
+ if (!onFetchFiles) {
4934
+ return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4935
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsx(react.Text, { color: "fg.muted", children: "Media library browser requires onFetchFiles" }) }));
4936
+ }
4937
+ const handleMediaLibrarySelect = (fileId) => {
4938
+ if (isSingleSelect) {
4939
+ setValue(colLabel, fileId);
4940
+ }
4941
+ else {
4942
+ const newFileIds = [...currentFileIds, fileId];
4943
+ setValue(colLabel, newFileIds);
4944
+ }
4945
+ };
4946
+ const handleRemove = (index) => {
4947
+ if (isSingleSelect) {
4948
+ setValue(colLabel, '');
4949
+ }
4950
+ else {
4951
+ const newFileIds = currentFileIds.filter((_, i) => i !== index);
4952
+ setValue(colLabel, newFileIds);
4953
+ }
4954
+ };
4955
+ const isImageId = (fileId) => {
4956
+ return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
4957
+ };
4958
+ return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4959
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: jsxRuntime.jsx(react.Button, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
4960
+ formI18n.t('browse_library') ??
4961
+ 'Browse from Library' }) }), jsxRuntime.jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
4962
+ formI18n.t('dialog_title') ??
4963
+ 'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel }), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
4964
+ const isImage = isImageId(fileId);
4965
+ const imageFailed = failedImageIds.has(fileId);
4966
+ return (jsxRuntime.jsx(react.Card.Root, { variant: 'subtle', colorPalette: "blue", children: jsxRuntime.jsxs(react.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: {
4967
+ borderColor: 'colorPalette.300',
4968
+ bg: 'bg.muted',
4969
+ }, transition: "all 0.2s", children: [jsxRuntime.jsx(react.Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, marginRight: "2", children: isImage && !imageFailed ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 6, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 6, color: "fg.muted" })) }), jsxRuntime.jsx(react.VStack, { align: "start", flex: 1, gap: 1, children: jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: fileId }) }), jsxRuntime.jsx(react.Icon, { as: ti.TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, `${fileId}-${index}`));
4970
+ }) })] }));
4971
+ };
4972
+
4838
4973
  const ToggleTip = React__namespace.forwardRef(function ToggleTip(props, ref) {
4839
4974
  const { showArrow, children, portalled = true, content, portalRef, ...rest } = props;
4840
4975
  return (jsxRuntime.jsxs(react.Popover.Root, { ...rest, positioning: { ...rest.positioning, gutter: 4 }, children: [jsxRuntime.jsx(react.Popover.Trigger, { asChild: true, children: children }), jsxRuntime.jsx(react.Portal, { disabled: !portalled, container: portalRef, children: jsxRuntime.jsx(react.Popover.Positioner, { children: jsxRuntime.jsxs(react.Popover.Content, { width: "auto", px: "2", py: "1", textStyle: "xs", rounded: "sm", ref: ref, children: [showArrow && (jsxRuntime.jsx(react.Popover.Arrow, { children: jsxRuntime.jsx(react.Popover.ArrowTip, {}) })), content] }) }) })] }));
@@ -6000,6 +6135,9 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
6000
6135
  if (variant === 'text-area') {
6001
6136
  return jsxRuntime.jsx(TextAreaInput, { schema: colSchema, prefix, column });
6002
6137
  }
6138
+ if (variant === 'media-library-browser') {
6139
+ return (jsxRuntime.jsx(FormMediaLibraryBrowser, { schema: colSchema, prefix, column }));
6140
+ }
6003
6141
  return jsxRuntime.jsx(StringInputField, { schema: colSchema, prefix, column });
6004
6142
  }
6005
6143
  if (type === 'number' || type === 'integer') {
@@ -6025,6 +6163,9 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
6025
6163
  if (variant === 'file-picker') {
6026
6164
  return jsxRuntime.jsx(FilePicker, { schema: colSchema, prefix, column });
6027
6165
  }
6166
+ if (variant === 'media-library-browser') {
6167
+ return (jsxRuntime.jsx(FormMediaLibraryBrowser, { schema: colSchema, prefix, column }));
6168
+ }
6028
6169
  if (variant === 'date-range') {
6029
6170
  return jsxRuntime.jsx(DateRangePicker, { schema: colSchema, prefix, column });
6030
6171
  }
@@ -6416,59 +6557,62 @@ const DateTimeViewer = ({ column, schema, prefix }) => {
6416
6557
  const SchemaViewer = ({ schema, prefix, column, }) => {
6417
6558
  const colSchema = schema;
6418
6559
  const { type, variant, properties: innerProperties, foreign_key, items, format, } = schema;
6419
- if (variant === "custom-input") {
6560
+ if (variant === 'custom-input') {
6420
6561
  return jsxRuntime.jsx(CustomViewer, { schema: colSchema, prefix, column });
6421
6562
  }
6422
- if (type === "string") {
6563
+ if (type === 'string') {
6423
6564
  if ((schema.enum ?? []).length > 0) {
6424
6565
  return jsxRuntime.jsx(EnumViewer, { schema: colSchema, prefix, column });
6425
6566
  }
6426
- if (variant === "id-picker") {
6567
+ if (variant === 'id-picker') {
6427
6568
  idPickerSanityCheck(column, foreign_key);
6428
6569
  return jsxRuntime.jsx(IdViewer, { schema: colSchema, prefix, column });
6429
6570
  }
6430
- if (format === "time") {
6571
+ if (format === 'time') {
6431
6572
  return jsxRuntime.jsx(TimeViewer, { schema: colSchema, prefix, column });
6432
6573
  }
6433
- if (format === "date") {
6574
+ if (format === 'date') {
6434
6575
  return jsxRuntime.jsx(DateViewer, { schema: colSchema, prefix, column });
6435
6576
  }
6436
- if (format === "date-time") {
6577
+ if (format === 'date-time') {
6437
6578
  return jsxRuntime.jsx(DateTimeViewer, { schema: colSchema, prefix, column });
6438
6579
  }
6439
- if (variant === "text-area") {
6580
+ if (variant === 'text-area') {
6440
6581
  return jsxRuntime.jsx(TextAreaViewer, { schema: colSchema, prefix, column });
6441
6582
  }
6442
6583
  return jsxRuntime.jsx(StringViewer, { schema: colSchema, prefix, column });
6443
6584
  }
6444
- if (type === "number" || type === "integer") {
6585
+ if (type === 'number' || type === 'integer') {
6445
6586
  return jsxRuntime.jsx(NumberViewer, { schema: colSchema, prefix, column });
6446
6587
  }
6447
- if (type === "boolean") {
6588
+ if (type === 'boolean') {
6448
6589
  return jsxRuntime.jsx(BooleanViewer, { schema: colSchema, prefix, column });
6449
6590
  }
6450
- if (type === "object") {
6591
+ if (type === 'object') {
6451
6592
  if (innerProperties) {
6452
6593
  return jsxRuntime.jsx(ObjectViewer, { schema: colSchema, prefix, column });
6453
6594
  }
6454
6595
  return jsxRuntime.jsx(RecordInput, { schema: colSchema, prefix, column });
6455
6596
  }
6456
- if (type === "array") {
6457
- if (variant === "id-picker") {
6597
+ if (type === 'array') {
6598
+ if (variant === 'id-picker') {
6458
6599
  idPickerSanityCheck(column, foreign_key);
6459
6600
  return (jsxRuntime.jsx(IdViewer, { schema: colSchema, prefix, column, isMultiple: true }));
6460
6601
  }
6461
- if (variant === "tag-picker") {
6602
+ if (variant === 'tag-picker') {
6462
6603
  return jsxRuntime.jsx(TagViewer, { schema: colSchema, prefix, column });
6463
6604
  }
6464
- if (variant === "file-picker") {
6605
+ if (variant === 'file-picker') {
6606
+ return jsxRuntime.jsx(FileViewer, { schema: colSchema, prefix, column });
6607
+ }
6608
+ if (variant === 'media-library-browser') {
6465
6609
  return jsxRuntime.jsx(FileViewer, { schema: colSchema, prefix, column });
6466
6610
  }
6467
- if (variant === "enum-picker") {
6611
+ if (variant === 'enum-picker') {
6468
6612
  const { items } = schema;
6469
6613
  const { enum: enumItems } = items;
6470
6614
  const enumSchema = {
6471
- type: "string",
6615
+ type: 'string',
6472
6616
  enum: enumItems,
6473
6617
  };
6474
6618
  return (jsxRuntime.jsx(EnumViewer, { isMultiple: true, schema: enumSchema, prefix, column }));
@@ -6478,7 +6622,7 @@ const SchemaViewer = ({ schema, prefix, column, }) => {
6478
6622
  }
6479
6623
  return jsxRuntime.jsx(react.Text, { children: `array ${column}` });
6480
6624
  }
6481
- if (type === "null") {
6625
+ if (type === 'null') {
6482
6626
  return jsxRuntime.jsx(react.Text, { children: `null ${column}` });
6483
6627
  }
6484
6628
  return jsxRuntime.jsx(react.Text, { children: "missing type" });
@@ -7409,6 +7553,7 @@ exports.FormBody = FormBody;
7409
7553
  exports.FormRoot = FormRoot;
7410
7554
  exports.FormTitle = FormTitle;
7411
7555
  exports.GlobalFilter = GlobalFilter;
7556
+ exports.MediaLibraryBrowser = MediaLibraryBrowser;
7412
7557
  exports.PageSizeControl = PageSizeControl;
7413
7558
  exports.Pagination = Pagination;
7414
7559
  exports.RecordDisplay = RecordDisplay;