@bsol-oss/react-datatable5 13.0.1-beta.13 → 13.0.1-beta.14

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
@@ -3203,44 +3203,46 @@ const Checkbox = React__namespace.forwardRef(function Checkbox(props, ref) {
3203
3203
  return (jsxRuntime.jsxs(react.Checkbox.Root, { ref: rootRef, ...rest, children: [jsxRuntime.jsx(react.Checkbox.HiddenInput, { ref: ref, ...inputProps }), jsxRuntime.jsx(react.Checkbox.Control, { children: icon || jsxRuntime.jsx(react.Checkbox.Indicator, {}) }), children != null && (jsxRuntime.jsx(react.Checkbox.Label, { children: children }))] }));
3204
3204
  });
3205
3205
 
3206
- // Generate a color based on column id for visual distinction
3207
- const getColorForColumn = (id) => {
3208
- const colors = [
3209
- 'blue',
3210
- 'green',
3211
- 'purple',
3212
- 'orange',
3213
- 'pink',
3214
- 'cyan',
3215
- 'teal',
3216
- 'red',
3217
- ];
3218
- let hash = 0;
3219
- for (let i = 0; i < id.length; i++) {
3220
- hash = id.charCodeAt(i) + ((hash << 5) - hash);
3221
- }
3222
- return colors[Math.abs(hash) % colors.length];
3223
- };
3224
- const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant, colorPalette, }) => {
3225
- const { table, tableLabel } = useDataTableContext();
3206
+ const ColumnFilterMenu = ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, }) => {
3226
3207
  const [searchTerm, setSearchTerm] = React.useState('');
3227
3208
  const debouncedSearchTerm = usehooks.useDebounce(searchTerm, 300);
3228
3209
  const [isOpen, setIsOpen] = React.useState(false);
3210
+ const [internalValue, setInternalValue] = React.useState(undefined);
3229
3211
  const [pendingFilterValue, setPendingFilterValue] = React.useState(undefined);
3230
3212
  const debouncedFilterValue = usehooks.useDebounce(pendingFilterValue, 300);
3231
3213
  const lastAppliedValueRef = React.useRef('__INITIAL__');
3232
- const column = table.getColumn(columnId);
3233
- const currentFilterValue = column?.getFilterValue();
3214
+ // Use controlled value if provided, otherwise use internal state
3215
+ const currentFilterValue = controlledValue !== undefined ? controlledValue : internalValue;
3234
3216
  const isArrayFilter = filterVariant === 'tag';
3235
- // Apply debounced filter value to column
3217
+ // Default labels
3218
+ const defaultLabels = {
3219
+ filterByLabel: 'Filter by',
3220
+ filterLabelsPlaceholder: 'Filter labels',
3221
+ noFiltersMatchText: 'No filters match your search',
3222
+ };
3223
+ const finalLabels = { ...defaultLabels, ...labels };
3224
+ // Apply debounced filter value via onChange callback
3236
3225
  React.useEffect(() => {
3237
3226
  const currentKey = JSON.stringify(debouncedFilterValue);
3238
3227
  // Only apply if the value has changed from what we last applied
3239
3228
  if (currentKey !== lastAppliedValueRef.current) {
3240
- column?.setFilterValue(debouncedFilterValue);
3229
+ if (onChange) {
3230
+ onChange(debouncedFilterValue);
3231
+ }
3232
+ else {
3233
+ setInternalValue(debouncedFilterValue);
3234
+ }
3241
3235
  lastAppliedValueRef.current = currentKey;
3242
3236
  }
3243
- }, [debouncedFilterValue, column]);
3237
+ }, [debouncedFilterValue, onChange]);
3238
+ // Sync internal value when controlled value changes
3239
+ React.useEffect(() => {
3240
+ if (controlledValue !== undefined) {
3241
+ setInternalValue(controlledValue);
3242
+ setPendingFilterValue(controlledValue);
3243
+ lastAppliedValueRef.current = JSON.stringify(controlledValue);
3244
+ }
3245
+ }, [controlledValue]);
3244
3246
  // Debounced filter update function
3245
3247
  const debouncedSetFilterValue = (value) => {
3246
3248
  setPendingFilterValue(value);
@@ -3262,9 +3264,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3262
3264
  const searchLower = debouncedSearchTerm.toLowerCase();
3263
3265
  return filterOptions.filter((option) => option.label.toLowerCase().includes(searchLower));
3264
3266
  }, [filterOptions, debouncedSearchTerm]);
3265
- if (!column)
3266
- return null;
3267
- return (jsxRuntime.jsxs(MenuRoot, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [jsxRuntime.jsx(MenuTrigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { variant: "outline", size: "sm", gap: 2, children: [jsxRuntime.jsx(react.Icon, { as: md.MdFilterList }), jsxRuntime.jsxs(react.Text, { children: [displayName, " ", activeCount > 0 && `(${activeCount})`] })] }) }), jsxRuntime.jsx(MenuContent, { maxW: "20rem", minW: "18rem", children: jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 2, p: 2, children: [jsxRuntime.jsxs(react.Heading, { size: "sm", px: 2, children: [tableLabel.filterByLabel, " ", displayName] }), jsxRuntime.jsx(InputGroup, { startElement: jsxRuntime.jsx(react.Icon, { as: md.MdSearch }), children: jsxRuntime.jsx(react.Input, { placeholder: tableLabel.filterLabelsPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), jsxRuntime.jsx(react.Box, { maxH: "20rem", overflowY: "auto", css: {
3267
+ return (jsxRuntime.jsxs(MenuRoot, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [jsxRuntime.jsx(MenuTrigger, { asChild: true, children: jsxRuntime.jsxs(react.Button, { variant: "outline", size: "sm", gap: 2, children: [jsxRuntime.jsx(react.Icon, { as: md.MdFilterList }), jsxRuntime.jsxs(react.Text, { children: [displayName, " ", activeCount > 0 && `(${activeCount})`] })] }) }), jsxRuntime.jsx(MenuContent, { maxW: "20rem", minW: "18rem", children: jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 2, p: 2, children: [jsxRuntime.jsxs(react.Heading, { size: "sm", px: 2, children: [finalLabels.filterByLabel, " ", displayName] }), jsxRuntime.jsx(InputGroup, { startElement: jsxRuntime.jsx(react.Icon, { as: md.MdSearch }), children: jsxRuntime.jsx(react.Input, { placeholder: finalLabels.filterLabelsPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), jsxRuntime.jsx(react.Box, { maxH: "20rem", overflowY: "auto", css: {
3268
3268
  '&::-webkit-scrollbar': {
3269
3269
  width: '8px',
3270
3270
  },
@@ -3278,7 +3278,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3278
3278
  '&::-webkit-scrollbar-thumb:hover': {
3279
3279
  background: 'var(--chakra-colors-border-subtle)',
3280
3280
  },
3281
- }, children: jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 1, children: filteredOptions.length === 0 ? (jsxRuntime.jsx(react.Text, { px: 2, py: 4, color: "fg.muted", textAlign: "center", children: tableLabel.noFiltersMatchText })) : (filteredOptions.map((option) => {
3281
+ }, children: jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 1, children: filteredOptions.length === 0 ? (jsxRuntime.jsx(react.Text, { px: 2, py: 4, color: "fg.muted", textAlign: "center", children: finalLabels.noFiltersMatchText })) : (filteredOptions.map((option) => {
3282
3282
  const isActive = isArrayFilter
3283
3283
  ? currentFilterValue?.includes(option.value) ?? false
3284
3284
  : currentFilterValue === option.value;
@@ -3356,8 +3356,27 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3356
3356
  } }) }), jsxRuntime.jsx(react.Box, { flex: 1, minW: 0, children: jsxRuntime.jsxs(react.HStack, { gap: 2, align: "center", children: [jsxRuntime.jsx(react.Box, { w: 3, h: 3, borderRadius: "full", bg: `${colorPalette}.500`, flexShrink: 0 }), jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", truncate: true, children: option.label })] }) })] }) }, option.value));
3357
3357
  })) }) })] }) })] }));
3358
3358
  };
3359
+
3360
+ // Generate a color based on column id for visual distinction
3361
+ const getColorForColumn = (id) => {
3362
+ const colors = [
3363
+ 'blue',
3364
+ 'green',
3365
+ 'purple',
3366
+ 'orange',
3367
+ 'pink',
3368
+ 'cyan',
3369
+ 'teal',
3370
+ 'red',
3371
+ ];
3372
+ let hash = 0;
3373
+ for (let i = 0; i < id.length; i++) {
3374
+ hash = id.charCodeAt(i) + ((hash << 5) - hash);
3375
+ }
3376
+ return colors[Math.abs(hash) % colors.length];
3377
+ };
3359
3378
  const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
3360
- const { table } = useDataTableContext();
3379
+ const { table, tableLabel } = useDataTableContext();
3361
3380
  // Get columns from filterTagsOptions
3362
3381
  const columnsWithFilters = React.useMemo(() => {
3363
3382
  if (filterTagsOptions.length === 0) {
@@ -3372,26 +3391,37 @@ const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
3372
3391
  const meta = column.columnDef.meta;
3373
3392
  const displayName = meta?.displayName ?? column.id;
3374
3393
  const filterVariant = meta?.filterVariant;
3394
+ if (!column) {
3395
+ return null;
3396
+ }
3375
3397
  return {
3376
3398
  columnId: option.column,
3377
3399
  displayName,
3378
3400
  filterOptions: option.options,
3379
- filterVariant: filterVariant === 'tag' ? 'tag' : 'select',
3401
+ filterVariant: (filterVariant === 'tag' ? 'tag' : 'select'),
3380
3402
  colorPalette: getColorForColumn(option.column),
3403
+ column,
3381
3404
  };
3382
3405
  })
3383
- .filter((col) => col !== null);
3406
+ .filter((col) => col !== null && col.column !== null && col.column !== undefined);
3384
3407
  }, [table, filterTagsOptions]);
3385
3408
  if (columnsWithFilters.length === 0) {
3386
3409
  return null;
3387
3410
  }
3388
- return (jsxRuntime.jsx(react.Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) => (jsxRuntime.jsx(ColumnFilterMenu, { columnId: column.columnId, displayName: column.displayName, filterOptions: column.filterOptions, filterVariant: column.filterVariant, colorPalette: column.colorPalette }, column.columnId))) }));
3411
+ return (jsxRuntime.jsx(react.Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) => {
3412
+ const filterValue = column.column.getFilterValue();
3413
+ return (jsxRuntime.jsx(ColumnFilterMenu, { displayName: column.displayName, filterOptions: column.filterOptions, filterVariant: column.filterVariant, colorPalette: column.colorPalette, value: filterValue, onChange: (value) => column.column.setFilterValue(value), labels: {
3414
+ filterByLabel: tableLabel.filterByLabel,
3415
+ filterLabelsPlaceholder: tableLabel.filterLabelsPlaceholder,
3416
+ noFiltersMatchText: tableLabel.noFiltersMatchText,
3417
+ } }, column.columnId));
3418
+ }) }));
3389
3419
  };
3390
3420
 
3391
3421
  const TableControls = ({ fitTableWidth = false, fitTableHeight = false, children = jsxRuntime.jsx(jsxRuntime.Fragment, {}), showGlobalFilter = false, showFilter = false, showFilterName = false, showFilterTags = false, showReload = false, showPagination = true, showPageSizeControl = true, showPageCountText = true, showView = true, filterTagsOptions = [], extraItems = jsxRuntime.jsx(jsxRuntime.Fragment, {}), loading = false, hasError = false, gridProps = {}, }) => {
3392
3422
  const { tableLabel, table } = useDataTableContext();
3393
3423
  const { hasErrorText } = tableLabel;
3394
- return (jsxRuntime.jsxs(react.Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 2, children: [jsxRuntime.jsxs(react.Flex, { justifyContent: 'space-between', children: [jsxRuntime.jsx(react.Box, { children: showView && jsxRuntime.jsx(ViewDialog, { icon: jsxRuntime.jsx(md.MdOutlineViewColumn, {}) }) }), jsxRuntime.jsxs(react.Flex, { gap: '0.5rem', alignItems: 'center', justifySelf: 'end', children: [loading && jsxRuntime.jsx(react.Spinner, { size: 'sm' }), hasError && (jsxRuntime.jsx(Tooltip, { content: hasErrorText, children: jsxRuntime.jsx(react.Icon, { as: bs.BsExclamationCircleFill, color: 'red.400' }) })), showGlobalFilter && jsxRuntime.jsx(GlobalFilter, {}), showFilter && jsxRuntime.jsx(FilterDialog, {}), showReload && jsxRuntime.jsx(ReloadButton, {}), extraItems] })] }), showFilterTags && (jsxRuntime.jsx(react.Flex, { children: jsxRuntime.jsx(TableFilterTags, { filterTagsOptions: filterTagsOptions }) }))] }), jsxRuntime.jsx(react.Grid, { children: children }), (showPageSizeControl || showPageCountText || showPagination) && (jsxRuntime.jsxs(react.Flex, { justifyContent: 'space-between', children: [jsxRuntime.jsxs(react.Flex, { gap: '1rem', alignItems: 'center', children: [showPageSizeControl && jsxRuntime.jsx(PageSizeControl, {}), showPageCountText && jsxRuntime.jsx(RowCountText, {})] }), jsxRuntime.jsx(react.Box, { justifySelf: 'end', children: showPagination && jsxRuntime.jsx(Pagination, {}) })] }))] }));
3424
+ return (jsxRuntime.jsxs(react.Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsxRuntime.jsxs(react.Flex, { flexFlow: 'column', gap: 2, children: [jsxRuntime.jsxs(react.Flex, { justifyContent: 'space-between', children: [jsxRuntime.jsx(react.Box, { children: showView && jsxRuntime.jsx(ViewDialog, { icon: jsxRuntime.jsx(md.MdOutlineViewColumn, {}) }) }), jsxRuntime.jsxs(react.Flex, { gap: '0.5rem', alignItems: 'center', justifySelf: 'end', children: [loading && jsxRuntime.jsx(react.Spinner, { size: 'sm' }), hasError && (jsxRuntime.jsx(Tooltip, { content: hasErrorText, children: jsxRuntime.jsx(react.Icon, { as: bs.BsExclamationCircleFill, color: 'red.400' }) })), showGlobalFilter && jsxRuntime.jsx(GlobalFilter, {}), showFilter && jsxRuntime.jsx(FilterDialog, {}), showReload && jsxRuntime.jsx(ReloadButton, {}), extraItems] })] }), showFilterTags && (jsxRuntime.jsx(TableFilterTags, { filterTagsOptions: filterTagsOptions }))] }), jsxRuntime.jsx(react.Grid, { children: children }), (showPageSizeControl || showPageCountText || showPagination) && (jsxRuntime.jsxs(react.Flex, { justifyContent: 'space-between', children: [jsxRuntime.jsxs(react.Flex, { gap: '1rem', alignItems: 'center', children: [showPageSizeControl && jsxRuntime.jsx(PageSizeControl, {}), showPageCountText && jsxRuntime.jsx(RowCountText, {})] }), jsxRuntime.jsx(react.Box, { justifySelf: 'end', children: showPagination && jsxRuntime.jsx(Pagination, {}) })] }))] }));
3395
3425
  };
3396
3426
 
3397
3427
  const EmptyState$1 = React__namespace.forwardRef(function EmptyState(props, ref) {
@@ -6152,15 +6182,29 @@ const defaultRenderDisplay = (item) => {
6152
6182
  for (const field of displayFields) {
6153
6183
  if (obj[field] !== undefined && obj[field] !== null) {
6154
6184
  const value = obj[field];
6155
- // Return the value if it's a string or number, otherwise stringify it
6185
+ // Return the value if it's a string or number
6156
6186
  if (typeof value === 'string' || typeof value === 'number') {
6157
6187
  return String(value);
6158
6188
  }
6189
+ // If the value is an object, show warning and recommend custom renderDisplay
6190
+ if (typeof value === 'object' &&
6191
+ !Array.isArray(value) &&
6192
+ !(value instanceof Date)) {
6193
+ console.warn(`[CustomJSONSchema7] Display field "${field}" contains an object value. Consider providing a custom \`renderDisplay\` function in your schema to properly render this item. Field: ${field}, Value: ${JSON.stringify(value).substring(0, 100)}${JSON.stringify(value).length > 100 ? '...' : ''}`);
6194
+ // Still return the stringified value for now
6195
+ return JSON.stringify(value);
6196
+ }
6159
6197
  }
6160
6198
  }
6161
6199
  // If no display field found, fall back to JSON.stringify
6162
6200
  return JSON.stringify(item);
6163
6201
  }
6202
+ // For strings that look like JSON, show warning and recommend custom renderDisplay
6203
+ if (typeof item === 'string' &&
6204
+ (item.trim().startsWith('{') || item.trim().startsWith('['))) {
6205
+ console.warn(`[CustomJSONSchema7] Item appears to be a JSON string. Consider providing a custom \`renderDisplay\` function in your schema to properly render this item. Current value: ${item.substring(0, 100)}${item.length > 100 ? '...' : ''}`);
6206
+ return item;
6207
+ }
6164
6208
  // For non-objects (primitives, arrays, dates), use JSON.stringify
6165
6209
  return JSON.stringify(item);
6166
6210
  };
package/dist/index.mjs CHANGED
@@ -3183,44 +3183,46 @@ const Checkbox = React.forwardRef(function Checkbox(props, ref) {
3183
3183
  return (jsxs(Checkbox$1.Root, { ref: rootRef, ...rest, children: [jsx(Checkbox$1.HiddenInput, { ref: ref, ...inputProps }), jsx(Checkbox$1.Control, { children: icon || jsx(Checkbox$1.Indicator, {}) }), children != null && (jsx(Checkbox$1.Label, { children: children }))] }));
3184
3184
  });
3185
3185
 
3186
- // Generate a color based on column id for visual distinction
3187
- const getColorForColumn = (id) => {
3188
- const colors = [
3189
- 'blue',
3190
- 'green',
3191
- 'purple',
3192
- 'orange',
3193
- 'pink',
3194
- 'cyan',
3195
- 'teal',
3196
- 'red',
3197
- ];
3198
- let hash = 0;
3199
- for (let i = 0; i < id.length; i++) {
3200
- hash = id.charCodeAt(i) + ((hash << 5) - hash);
3201
- }
3202
- return colors[Math.abs(hash) % colors.length];
3203
- };
3204
- const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant, colorPalette, }) => {
3205
- const { table, tableLabel } = useDataTableContext();
3186
+ const ColumnFilterMenu = ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, }) => {
3206
3187
  const [searchTerm, setSearchTerm] = useState('');
3207
3188
  const debouncedSearchTerm = useDebounce(searchTerm, 300);
3208
3189
  const [isOpen, setIsOpen] = useState(false);
3190
+ const [internalValue, setInternalValue] = useState(undefined);
3209
3191
  const [pendingFilterValue, setPendingFilterValue] = useState(undefined);
3210
3192
  const debouncedFilterValue = useDebounce(pendingFilterValue, 300);
3211
3193
  const lastAppliedValueRef = useRef('__INITIAL__');
3212
- const column = table.getColumn(columnId);
3213
- const currentFilterValue = column?.getFilterValue();
3194
+ // Use controlled value if provided, otherwise use internal state
3195
+ const currentFilterValue = controlledValue !== undefined ? controlledValue : internalValue;
3214
3196
  const isArrayFilter = filterVariant === 'tag';
3215
- // Apply debounced filter value to column
3197
+ // Default labels
3198
+ const defaultLabels = {
3199
+ filterByLabel: 'Filter by',
3200
+ filterLabelsPlaceholder: 'Filter labels',
3201
+ noFiltersMatchText: 'No filters match your search',
3202
+ };
3203
+ const finalLabels = { ...defaultLabels, ...labels };
3204
+ // Apply debounced filter value via onChange callback
3216
3205
  useEffect(() => {
3217
3206
  const currentKey = JSON.stringify(debouncedFilterValue);
3218
3207
  // Only apply if the value has changed from what we last applied
3219
3208
  if (currentKey !== lastAppliedValueRef.current) {
3220
- column?.setFilterValue(debouncedFilterValue);
3209
+ if (onChange) {
3210
+ onChange(debouncedFilterValue);
3211
+ }
3212
+ else {
3213
+ setInternalValue(debouncedFilterValue);
3214
+ }
3221
3215
  lastAppliedValueRef.current = currentKey;
3222
3216
  }
3223
- }, [debouncedFilterValue, column]);
3217
+ }, [debouncedFilterValue, onChange]);
3218
+ // Sync internal value when controlled value changes
3219
+ useEffect(() => {
3220
+ if (controlledValue !== undefined) {
3221
+ setInternalValue(controlledValue);
3222
+ setPendingFilterValue(controlledValue);
3223
+ lastAppliedValueRef.current = JSON.stringify(controlledValue);
3224
+ }
3225
+ }, [controlledValue]);
3224
3226
  // Debounced filter update function
3225
3227
  const debouncedSetFilterValue = (value) => {
3226
3228
  setPendingFilterValue(value);
@@ -3242,9 +3244,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3242
3244
  const searchLower = debouncedSearchTerm.toLowerCase();
3243
3245
  return filterOptions.filter((option) => option.label.toLowerCase().includes(searchLower));
3244
3246
  }, [filterOptions, debouncedSearchTerm]);
3245
- if (!column)
3246
- return null;
3247
- return (jsxs(MenuRoot, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [jsx(MenuTrigger, { asChild: true, children: jsxs(Button$1, { variant: "outline", size: "sm", gap: 2, children: [jsx(Icon, { as: MdFilterList }), jsxs(Text, { children: [displayName, " ", activeCount > 0 && `(${activeCount})`] })] }) }), jsx(MenuContent, { maxW: "20rem", minW: "18rem", children: jsxs(VStack, { align: "stretch", gap: 2, p: 2, children: [jsxs(Heading, { size: "sm", px: 2, children: [tableLabel.filterByLabel, " ", displayName] }), jsx(InputGroup, { startElement: jsx(Icon, { as: MdSearch }), children: jsx(Input, { placeholder: tableLabel.filterLabelsPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), jsx(Box, { maxH: "20rem", overflowY: "auto", css: {
3247
+ return (jsxs(MenuRoot, { open: isOpen, onOpenChange: (details) => setIsOpen(details.open), children: [jsx(MenuTrigger, { asChild: true, children: jsxs(Button$1, { variant: "outline", size: "sm", gap: 2, children: [jsx(Icon, { as: MdFilterList }), jsxs(Text, { children: [displayName, " ", activeCount > 0 && `(${activeCount})`] })] }) }), jsx(MenuContent, { maxW: "20rem", minW: "18rem", children: jsxs(VStack, { align: "stretch", gap: 2, p: 2, children: [jsxs(Heading, { size: "sm", px: 2, children: [finalLabels.filterByLabel, " ", displayName] }), jsx(InputGroup, { startElement: jsx(Icon, { as: MdSearch }), children: jsx(Input, { placeholder: finalLabels.filterLabelsPlaceholder, value: searchTerm, onChange: (e) => setSearchTerm(e.target.value) }) }), jsx(Box, { maxH: "20rem", overflowY: "auto", css: {
3248
3248
  '&::-webkit-scrollbar': {
3249
3249
  width: '8px',
3250
3250
  },
@@ -3258,7 +3258,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3258
3258
  '&::-webkit-scrollbar-thumb:hover': {
3259
3259
  background: 'var(--chakra-colors-border-subtle)',
3260
3260
  },
3261
- }, children: jsx(VStack, { align: "stretch", gap: 1, children: filteredOptions.length === 0 ? (jsx(Text, { px: 2, py: 4, color: "fg.muted", textAlign: "center", children: tableLabel.noFiltersMatchText })) : (filteredOptions.map((option) => {
3261
+ }, children: jsx(VStack, { align: "stretch", gap: 1, children: filteredOptions.length === 0 ? (jsx(Text, { px: 2, py: 4, color: "fg.muted", textAlign: "center", children: finalLabels.noFiltersMatchText })) : (filteredOptions.map((option) => {
3262
3262
  const isActive = isArrayFilter
3263
3263
  ? currentFilterValue?.includes(option.value) ?? false
3264
3264
  : currentFilterValue === option.value;
@@ -3336,8 +3336,27 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3336
3336
  } }) }), jsx(Box, { flex: 1, minW: 0, children: jsxs(HStack, { gap: 2, align: "center", children: [jsx(Box, { w: 3, h: 3, borderRadius: "full", bg: `${colorPalette}.500`, flexShrink: 0 }), jsx(Text, { fontSize: "sm", fontWeight: "medium", truncate: true, children: option.label })] }) })] }) }, option.value));
3337
3337
  })) }) })] }) })] }));
3338
3338
  };
3339
+
3340
+ // Generate a color based on column id for visual distinction
3341
+ const getColorForColumn = (id) => {
3342
+ const colors = [
3343
+ 'blue',
3344
+ 'green',
3345
+ 'purple',
3346
+ 'orange',
3347
+ 'pink',
3348
+ 'cyan',
3349
+ 'teal',
3350
+ 'red',
3351
+ ];
3352
+ let hash = 0;
3353
+ for (let i = 0; i < id.length; i++) {
3354
+ hash = id.charCodeAt(i) + ((hash << 5) - hash);
3355
+ }
3356
+ return colors[Math.abs(hash) % colors.length];
3357
+ };
3339
3358
  const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
3340
- const { table } = useDataTableContext();
3359
+ const { table, tableLabel } = useDataTableContext();
3341
3360
  // Get columns from filterTagsOptions
3342
3361
  const columnsWithFilters = useMemo(() => {
3343
3362
  if (filterTagsOptions.length === 0) {
@@ -3352,26 +3371,37 @@ const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
3352
3371
  const meta = column.columnDef.meta;
3353
3372
  const displayName = meta?.displayName ?? column.id;
3354
3373
  const filterVariant = meta?.filterVariant;
3374
+ if (!column) {
3375
+ return null;
3376
+ }
3355
3377
  return {
3356
3378
  columnId: option.column,
3357
3379
  displayName,
3358
3380
  filterOptions: option.options,
3359
- filterVariant: filterVariant === 'tag' ? 'tag' : 'select',
3381
+ filterVariant: (filterVariant === 'tag' ? 'tag' : 'select'),
3360
3382
  colorPalette: getColorForColumn(option.column),
3383
+ column,
3361
3384
  };
3362
3385
  })
3363
- .filter((col) => col !== null);
3386
+ .filter((col) => col !== null && col.column !== null && col.column !== undefined);
3364
3387
  }, [table, filterTagsOptions]);
3365
3388
  if (columnsWithFilters.length === 0) {
3366
3389
  return null;
3367
3390
  }
3368
- return (jsx(Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) => (jsx(ColumnFilterMenu, { columnId: column.columnId, displayName: column.displayName, filterOptions: column.filterOptions, filterVariant: column.filterVariant, colorPalette: column.colorPalette }, column.columnId))) }));
3391
+ return (jsx(Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) => {
3392
+ const filterValue = column.column.getFilterValue();
3393
+ return (jsx(ColumnFilterMenu, { displayName: column.displayName, filterOptions: column.filterOptions, filterVariant: column.filterVariant, colorPalette: column.colorPalette, value: filterValue, onChange: (value) => column.column.setFilterValue(value), labels: {
3394
+ filterByLabel: tableLabel.filterByLabel,
3395
+ filterLabelsPlaceholder: tableLabel.filterLabelsPlaceholder,
3396
+ noFiltersMatchText: tableLabel.noFiltersMatchText,
3397
+ } }, column.columnId));
3398
+ }) }));
3369
3399
  };
3370
3400
 
3371
3401
  const TableControls = ({ fitTableWidth = false, fitTableHeight = false, children = jsx(Fragment, {}), showGlobalFilter = false, showFilter = false, showFilterName = false, showFilterTags = false, showReload = false, showPagination = true, showPageSizeControl = true, showPageCountText = true, showView = true, filterTagsOptions = [], extraItems = jsx(Fragment, {}), loading = false, hasError = false, gridProps = {}, }) => {
3372
3402
  const { tableLabel, table } = useDataTableContext();
3373
3403
  const { hasErrorText } = tableLabel;
3374
- return (jsxs(Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsxs(Flex, { flexFlow: 'column', gap: 2, children: [jsxs(Flex, { justifyContent: 'space-between', children: [jsx(Box, { children: showView && jsx(ViewDialog, { icon: jsx(MdOutlineViewColumn, {}) }) }), jsxs(Flex, { gap: '0.5rem', alignItems: 'center', justifySelf: 'end', children: [loading && jsx(Spinner, { size: 'sm' }), hasError && (jsx(Tooltip, { content: hasErrorText, children: jsx(Icon, { as: BsExclamationCircleFill, color: 'red.400' }) })), showGlobalFilter && jsx(GlobalFilter, {}), showFilter && jsx(FilterDialog, {}), showReload && jsx(ReloadButton, {}), extraItems] })] }), showFilterTags && (jsx(Flex, { children: jsx(TableFilterTags, { filterTagsOptions: filterTagsOptions }) }))] }), jsx(Grid, { children: children }), (showPageSizeControl || showPageCountText || showPagination) && (jsxs(Flex, { justifyContent: 'space-between', children: [jsxs(Flex, { gap: '1rem', alignItems: 'center', children: [showPageSizeControl && jsx(PageSizeControl, {}), showPageCountText && jsx(RowCountText, {})] }), jsx(Box, { justifySelf: 'end', children: showPagination && jsx(Pagination, {}) })] }))] }));
3404
+ return (jsxs(Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsxs(Flex, { flexFlow: 'column', gap: 2, children: [jsxs(Flex, { justifyContent: 'space-between', children: [jsx(Box, { children: showView && jsx(ViewDialog, { icon: jsx(MdOutlineViewColumn, {}) }) }), jsxs(Flex, { gap: '0.5rem', alignItems: 'center', justifySelf: 'end', children: [loading && jsx(Spinner, { size: 'sm' }), hasError && (jsx(Tooltip, { content: hasErrorText, children: jsx(Icon, { as: BsExclamationCircleFill, color: 'red.400' }) })), showGlobalFilter && jsx(GlobalFilter, {}), showFilter && jsx(FilterDialog, {}), showReload && jsx(ReloadButton, {}), extraItems] })] }), showFilterTags && (jsx(TableFilterTags, { filterTagsOptions: filterTagsOptions }))] }), jsx(Grid, { children: children }), (showPageSizeControl || showPageCountText || showPagination) && (jsxs(Flex, { justifyContent: 'space-between', children: [jsxs(Flex, { gap: '1rem', alignItems: 'center', children: [showPageSizeControl && jsx(PageSizeControl, {}), showPageCountText && jsx(RowCountText, {})] }), jsx(Box, { justifySelf: 'end', children: showPagination && jsx(Pagination, {}) })] }))] }));
3375
3405
  };
3376
3406
 
3377
3407
  const EmptyState$1 = React.forwardRef(function EmptyState(props, ref) {
@@ -6132,15 +6162,29 @@ const defaultRenderDisplay = (item) => {
6132
6162
  for (const field of displayFields) {
6133
6163
  if (obj[field] !== undefined && obj[field] !== null) {
6134
6164
  const value = obj[field];
6135
- // Return the value if it's a string or number, otherwise stringify it
6165
+ // Return the value if it's a string or number
6136
6166
  if (typeof value === 'string' || typeof value === 'number') {
6137
6167
  return String(value);
6138
6168
  }
6169
+ // If the value is an object, show warning and recommend custom renderDisplay
6170
+ if (typeof value === 'object' &&
6171
+ !Array.isArray(value) &&
6172
+ !(value instanceof Date)) {
6173
+ console.warn(`[CustomJSONSchema7] Display field "${field}" contains an object value. Consider providing a custom \`renderDisplay\` function in your schema to properly render this item. Field: ${field}, Value: ${JSON.stringify(value).substring(0, 100)}${JSON.stringify(value).length > 100 ? '...' : ''}`);
6174
+ // Still return the stringified value for now
6175
+ return JSON.stringify(value);
6176
+ }
6139
6177
  }
6140
6178
  }
6141
6179
  // If no display field found, fall back to JSON.stringify
6142
6180
  return JSON.stringify(item);
6143
6181
  }
6182
+ // For strings that look like JSON, show warning and recommend custom renderDisplay
6183
+ if (typeof item === 'string' &&
6184
+ (item.trim().startsWith('{') || item.trim().startsWith('['))) {
6185
+ console.warn(`[CustomJSONSchema7] Item appears to be a JSON string. Consider providing a custom \`renderDisplay\` function in your schema to properly render this item. Current value: ${item.substring(0, 100)}${item.length > 100 ? '...' : ''}`);
6186
+ return item;
6187
+ }
6144
6188
  // For non-objects (primitives, arrays, dates), use JSON.stringify
6145
6189
  return JSON.stringify(item);
6146
6190
  };
@@ -0,0 +1,18 @@
1
+ export interface ColumnFilterMenuLabels {
2
+ filterByLabel?: string;
3
+ filterLabelsPlaceholder?: string;
4
+ noFiltersMatchText?: string;
5
+ }
6
+ export interface ColumnFilterMenuProps {
7
+ displayName: string;
8
+ filterOptions: {
9
+ label: string;
10
+ value: string;
11
+ }[];
12
+ filterVariant?: 'select' | 'tag';
13
+ colorPalette: string;
14
+ value?: string[] | string | undefined;
15
+ onChange?: (value: string[] | string | undefined) => void;
16
+ labels?: ColumnFilterMenuLabels;
17
+ }
18
+ export declare const ColumnFilterMenu: ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, }: ColumnFilterMenuProps) => import("react/jsx-runtime").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsol-oss/react-datatable5",
3
- "version": "13.0.1-beta.13",
3
+ "version": "13.0.1-beta.14",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",