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

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.d.ts CHANGED
@@ -130,8 +130,6 @@ interface TableControlsProps {
130
130
  children?: ReactNode;
131
131
  showGlobalFilter?: boolean;
132
132
  showFilter?: boolean;
133
- showFilterName?: boolean;
134
- showFilterTags?: boolean;
135
133
  showReload?: boolean;
136
134
  showPagination?: boolean;
137
135
  showPageSizeControl?: boolean;
@@ -149,7 +147,7 @@ interface TableControlsProps {
149
147
  hasError?: boolean;
150
148
  gridProps?: GridProps;
151
149
  }
152
- declare const TableControls: ({ fitTableWidth, fitTableHeight, children, showGlobalFilter, showFilter, showFilterName, showFilterTags, showReload, showPagination, showPageSizeControl, showPageCountText, showView, filterTagsOptions, extraItems, loading, hasError, gridProps, }: TableControlsProps) => react_jsx_runtime.JSX.Element;
150
+ declare const TableControls: ({ fitTableWidth, fitTableHeight, children, showGlobalFilter, showFilter, showReload, showPagination, showPageSizeControl, showPageCountText, showView, filterTagsOptions, extraItems, loading, hasError, gridProps, }: TableControlsProps) => react_jsx_runtime.JSX.Element;
153
151
 
154
152
  declare const TableFilter: () => react_jsx_runtime.JSX.Element;
155
153
 
package/dist/index.js CHANGED
@@ -3198,52 +3198,55 @@ const TableSelector = () => {
3198
3198
  }, "aria-label": 'reset selection', children: jsxRuntime.jsx(md.MdClear, {}) }))] }));
3199
3199
  };
3200
3200
 
3201
+ const Tag = React__namespace.forwardRef(function Tag(props, ref) {
3202
+ const { startElement, endElement, onClose, closable = !!onClose, children, ...rest } = props;
3203
+ return (jsxRuntime.jsxs(react.Tag.Root, { ref: ref, ...rest, children: [startElement && (jsxRuntime.jsx(react.Tag.StartElement, { children: startElement })), jsxRuntime.jsx(react.Tag.Label, { children: children }), endElement && (jsxRuntime.jsx(react.Tag.EndElement, { children: endElement })), closable && (jsxRuntime.jsx(react.Tag.EndElement, { children: jsxRuntime.jsx(react.Tag.CloseTrigger, { onClick: onClose }) }))] }));
3204
+ });
3205
+
3201
3206
  const Checkbox = React__namespace.forwardRef(function Checkbox(props, ref) {
3202
3207
  const { icon, children, inputProps, rootRef, ...rest } = props;
3203
3208
  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
3209
  });
3205
3210
 
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();
3211
+ const ColumnFilterMenu = ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, open: controlledOpen, onOpenChange, }) => {
3226
3212
  const [searchTerm, setSearchTerm] = React.useState('');
3227
3213
  const debouncedSearchTerm = usehooks.useDebounce(searchTerm, 300);
3228
- const [isOpen, setIsOpen] = React.useState(false);
3229
- const [pendingFilterValue, setPendingFilterValue] = React.useState(undefined);
3230
- const debouncedFilterValue = usehooks.useDebounce(pendingFilterValue, 300);
3231
- const lastAppliedValueRef = React.useRef('__INITIAL__');
3232
- const column = table.getColumn(columnId);
3233
- const currentFilterValue = column?.getFilterValue();
3214
+ const [internalOpen, setInternalOpen] = React.useState(false);
3215
+ // Use controlled open state if provided, otherwise use internal state
3216
+ const isOpen = controlledOpen !== undefined ? controlledOpen : internalOpen;
3217
+ const handleOpenChange = (details) => {
3218
+ if (onOpenChange) {
3219
+ onOpenChange(details.open);
3220
+ }
3221
+ else {
3222
+ setInternalOpen(details.open);
3223
+ }
3224
+ };
3225
+ const [internalValue, setInternalValue] = React.useState(undefined);
3226
+ // Use controlled value if provided, otherwise use internal state
3227
+ const currentFilterValue = controlledValue !== undefined ? controlledValue : internalValue;
3234
3228
  const isArrayFilter = filterVariant === 'tag';
3235
- // Apply debounced filter value to column
3229
+ // Default labels
3230
+ const defaultLabels = {
3231
+ filterByLabel: 'Filter by',
3232
+ filterLabelsPlaceholder: 'Filter labels',
3233
+ noFiltersMatchText: 'No filters match your search',
3234
+ };
3235
+ const finalLabels = { ...defaultLabels, ...labels };
3236
+ // Reset search term when menu closes
3236
3237
  React.useEffect(() => {
3237
- const currentKey = JSON.stringify(debouncedFilterValue);
3238
- // Only apply if the value has changed from what we last applied
3239
- if (currentKey !== lastAppliedValueRef.current) {
3240
- column?.setFilterValue(debouncedFilterValue);
3241
- lastAppliedValueRef.current = currentKey;
3242
- }
3243
- }, [debouncedFilterValue, column]);
3244
- // Debounced filter update function
3245
- const debouncedSetFilterValue = (value) => {
3246
- setPendingFilterValue(value);
3238
+ if (!isOpen) {
3239
+ setSearchTerm('');
3240
+ }
3241
+ }, [isOpen]);
3242
+ // Filter update function
3243
+ const setFilterValue = (value) => {
3244
+ if (onChange) {
3245
+ onChange(value);
3246
+ }
3247
+ else {
3248
+ setInternalValue(value);
3249
+ }
3247
3250
  };
3248
3251
  // Get active count for this column
3249
3252
  const activeCount = React.useMemo(() => {
@@ -3262,9 +3265,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3262
3265
  const searchLower = debouncedSearchTerm.toLowerCase();
3263
3266
  return filterOptions.filter((option) => option.label.toLowerCase().includes(searchLower));
3264
3267
  }, [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: {
3268
+ return (jsxRuntime.jsxs(MenuRoot, { open: isOpen, onOpenChange: handleOpenChange, 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
3269
  '&::-webkit-scrollbar': {
3269
3270
  width: '8px',
3270
3271
  },
@@ -3278,7 +3279,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3278
3279
  '&::-webkit-scrollbar-thumb:hover': {
3279
3280
  background: 'var(--chakra-colors-border-subtle)',
3280
3281
  },
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) => {
3282
+ }, 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
3283
  const isActive = isArrayFilter
3283
3284
  ? currentFilterValue?.includes(option.value) ?? false
3284
3285
  : currentFilterValue === option.value;
@@ -3290,29 +3291,26 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3290
3291
  // Remove from filter
3291
3292
  const newArray = currentArray.filter((v) => v !== option.value);
3292
3293
  if (newArray.length === 0) {
3293
- debouncedSetFilterValue(undefined);
3294
+ setFilterValue(undefined);
3294
3295
  }
3295
3296
  else {
3296
- debouncedSetFilterValue(newArray);
3297
+ setFilterValue(newArray);
3297
3298
  }
3298
3299
  }
3299
3300
  else {
3300
3301
  // Add to filter
3301
3302
  if (!currentArray.includes(option.value)) {
3302
- debouncedSetFilterValue([
3303
- ...currentArray,
3304
- option.value,
3305
- ]);
3303
+ setFilterValue([...currentArray, option.value]);
3306
3304
  }
3307
3305
  }
3308
3306
  }
3309
3307
  else {
3310
3308
  // Handle single value filters (select variant)
3311
3309
  if (isActive) {
3312
- debouncedSetFilterValue(undefined);
3310
+ setFilterValue(undefined);
3313
3311
  }
3314
3312
  else {
3315
- debouncedSetFilterValue(option.value);
3313
+ setFilterValue(option.value);
3316
3314
  }
3317
3315
  }
3318
3316
  };
@@ -3327,7 +3325,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3327
3325
  if (details.checked) {
3328
3326
  // Add to filter
3329
3327
  if (!currentArray.includes(option.value)) {
3330
- debouncedSetFilterValue([
3328
+ setFilterValue([
3331
3329
  ...currentArray,
3332
3330
  option.value,
3333
3331
  ]);
@@ -3337,27 +3335,46 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3337
3335
  // Remove from filter
3338
3336
  const newArray = currentArray.filter((v) => v !== option.value);
3339
3337
  if (newArray.length === 0) {
3340
- debouncedSetFilterValue(undefined);
3338
+ setFilterValue(undefined);
3341
3339
  }
3342
3340
  else {
3343
- debouncedSetFilterValue(newArray);
3341
+ setFilterValue(newArray);
3344
3342
  }
3345
3343
  }
3346
3344
  }
3347
3345
  else {
3348
3346
  // Handle single value filters (select variant)
3349
3347
  if (details.checked) {
3350
- debouncedSetFilterValue(option.value);
3348
+ setFilterValue(option.value);
3351
3349
  }
3352
3350
  else {
3353
- debouncedSetFilterValue(undefined);
3351
+ setFilterValue(undefined);
3354
3352
  }
3355
3353
  }
3356
3354
  } }) }), 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
3355
  })) }) })] }) })] }));
3358
3356
  };
3357
+
3358
+ // Generate a color based on column id for visual distinction
3359
+ const getColorForColumn = (id) => {
3360
+ const colors = [
3361
+ 'blue',
3362
+ 'green',
3363
+ 'purple',
3364
+ 'orange',
3365
+ 'pink',
3366
+ 'cyan',
3367
+ 'teal',
3368
+ 'red',
3369
+ ];
3370
+ let hash = 0;
3371
+ for (let i = 0; i < id.length; i++) {
3372
+ hash = id.charCodeAt(i) + ((hash << 5) - hash);
3373
+ }
3374
+ return colors[Math.abs(hash) % colors.length];
3375
+ };
3359
3376
  const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
3360
- const { table } = useDataTableContext();
3377
+ const { table, tableLabel } = useDataTableContext();
3361
3378
  // Get columns from filterTagsOptions
3362
3379
  const columnsWithFilters = React.useMemo(() => {
3363
3380
  if (filterTagsOptions.length === 0) {
@@ -3372,26 +3389,91 @@ const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
3372
3389
  const meta = column.columnDef.meta;
3373
3390
  const displayName = meta?.displayName ?? column.id;
3374
3391
  const filterVariant = meta?.filterVariant;
3392
+ if (!column) {
3393
+ return null;
3394
+ }
3375
3395
  return {
3376
3396
  columnId: option.column,
3377
3397
  displayName,
3378
3398
  filterOptions: option.options,
3379
- filterVariant: filterVariant === 'tag' ? 'tag' : 'select',
3399
+ filterVariant: (filterVariant === 'tag' ? 'tag' : 'select'),
3380
3400
  colorPalette: getColorForColumn(option.column),
3401
+ column,
3381
3402
  };
3382
3403
  })
3383
- .filter((col) => col !== null);
3404
+ .filter((col) => col !== null && col.column !== null && col.column !== undefined);
3384
3405
  }, [table, filterTagsOptions]);
3385
3406
  if (columnsWithFilters.length === 0) {
3386
3407
  return null;
3387
3408
  }
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))) }));
3409
+ return (jsxRuntime.jsx(react.Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) => {
3410
+ const filterValue = column.column.getFilterValue();
3411
+ 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: {
3412
+ filterByLabel: tableLabel.filterByLabel,
3413
+ filterLabelsPlaceholder: tableLabel.filterLabelsPlaceholder,
3414
+ noFiltersMatchText: tableLabel.noFiltersMatchText,
3415
+ } }, column.columnId));
3416
+ }) }));
3389
3417
  };
3390
3418
 
3391
- 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
- const { tableLabel, table } = useDataTableContext();
3419
+ const TableControls = ({ fitTableWidth = false, fitTableHeight = false, children = jsxRuntime.jsx(jsxRuntime.Fragment, {}), showGlobalFilter = false, showFilter = false, showReload = false, showPagination = true, showPageSizeControl = true, showPageCountText = true, showView = true, filterTagsOptions = [], extraItems = jsxRuntime.jsx(jsxRuntime.Fragment, {}), loading = false, hasError = false, gridProps = {}, }) => {
3420
+ const { tableLabel, table, columnFilters, setColumnFilters } = useDataTableContext();
3393
3421
  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, {}) })] }))] }));
3422
+ // Get applied filters with display information
3423
+ const appliedFilters = React.useMemo(() => {
3424
+ return columnFilters
3425
+ .map((filter) => {
3426
+ const column = table.getColumn(filter.id);
3427
+ if (!column)
3428
+ return null;
3429
+ const meta = column.columnDef.meta;
3430
+ const displayName = meta?.displayName ?? filter.id;
3431
+ const filterValue = filter.value;
3432
+ // Handle array values (tag filters)
3433
+ if (Array.isArray(filterValue)) {
3434
+ return {
3435
+ columnId: filter.id,
3436
+ displayName,
3437
+ values: filterValue,
3438
+ isArray: true,
3439
+ };
3440
+ }
3441
+ // Handle single values (select filters)
3442
+ if (filterValue !== undefined &&
3443
+ filterValue !== null &&
3444
+ filterValue !== '') {
3445
+ return {
3446
+ columnId: filter.id,
3447
+ displayName,
3448
+ value: String(filterValue),
3449
+ isArray: false,
3450
+ };
3451
+ }
3452
+ return null;
3453
+ })
3454
+ .filter((filter) => filter !== null);
3455
+ }, [columnFilters, table]);
3456
+ const handleRemoveFilter = (columnId) => {
3457
+ setColumnFilters(columnFilters.filter((f) => f.id !== columnId));
3458
+ };
3459
+ 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.jsx(react.Flex, { flexFlow: 'column', gap: 2, children: jsxRuntime.jsxs(react.Flex, { justifyContent: 'space-between', children: [jsxRuntime.jsxs(react.Flex, { gap: 2, alignItems: 'center', flexWrap: 'wrap', children: [showView && jsxRuntime.jsx(ViewDialog, { icon: jsxRuntime.jsx(md.MdOutlineViewColumn, {}) }), appliedFilters.length > 0 && (jsxRuntime.jsx(react.Flex, { gap: 1.5, alignItems: 'center', flexWrap: 'wrap', children: appliedFilters.map((filter) => {
3460
+ if (filter.isArray) {
3461
+ return filter.values.map((value, index) => (jsxRuntime.jsxs(Tag, { size: "sm", colorPalette: "blue", onClose: () => {
3462
+ const column = table.getColumn(filter.columnId);
3463
+ if (column) {
3464
+ const currentValue = column.getFilterValue() ?? [];
3465
+ const newValue = currentValue.filter((v) => v !== value);
3466
+ if (newValue.length === 0) {
3467
+ handleRemoveFilter(filter.columnId);
3468
+ }
3469
+ else {
3470
+ column.setFilterValue(newValue);
3471
+ }
3472
+ }
3473
+ }, children: [filter.displayName, ": ", value] }, `${filter.columnId}-${value}-${index}`)));
3474
+ }
3475
+ return (jsxRuntime.jsxs(Tag, { size: "sm", colorPalette: "blue", onClose: () => handleRemoveFilter(filter.columnId), children: [filter.displayName, ": ", filter.value] }, filter.columnId));
3476
+ }) }))] }), 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, {}), filterTagsOptions.length > 0 && (jsxRuntime.jsx(TableFilterTags, { filterTagsOptions: filterTagsOptions })), showFilter && jsxRuntime.jsx(FilterDialog, {}), showReload && jsxRuntime.jsx(ReloadButton, {}), extraItems] })] }) }), 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
3477
  };
3396
3478
 
3397
3479
  const EmptyState$1 = React__namespace.forwardRef(function EmptyState(props, ref) {
@@ -3959,11 +4041,6 @@ label, containerProps = {}, textProps = {}, children, }) => {
3959
4041
  return (jsxRuntime.jsx(react.Box, { textOverflow: "ellipsis", whiteSpace: "nowrap", wordBreak: "break-all", overflow: "auto", display: "flex", alignItems: "center", justifyContent: alignEnd ? 'flex-end' : undefined, height: "100%", textAlign: alignEnd ? 'right' : undefined, children: jsxRuntime.jsx(RenderValue, { text: displayValue, href: href, onClick: onClick, isCopyable: isCopyable, isBadge: isBadge, badgeColor: badgeColor, colorPalette: colorPalette, globalFilter: globalFilter, alignEnd: alignEnd }) }));
3960
4042
  };
3961
4043
 
3962
- const Tag = React__namespace.forwardRef(function Tag(props, ref) {
3963
- const { startElement, endElement, onClose, closable = !!onClose, children, ...rest } = props;
3964
- return (jsxRuntime.jsxs(react.Tag.Root, { ref: ref, ...rest, children: [startElement && (jsxRuntime.jsx(react.Tag.StartElement, { children: startElement })), jsxRuntime.jsx(react.Tag.Label, { children: children }), endElement && (jsxRuntime.jsx(react.Tag.EndElement, { children: endElement })), closable && (jsxRuntime.jsx(react.Tag.EndElement, { children: jsxRuntime.jsx(react.Tag.CloseTrigger, { onClick: onClose }) }))] }));
3965
- });
3966
-
3967
4044
  const CardHeader = ({ row, imageColumnId = undefined, titleColumnId = undefined, tagColumnId = undefined, tagIcon = undefined, showTag = true, imageProps = {}, }) => {
3968
4045
  if (!!row.original === false) {
3969
4046
  return jsxRuntime.jsx(jsxRuntime.Fragment, {});
@@ -6152,15 +6229,29 @@ const defaultRenderDisplay = (item) => {
6152
6229
  for (const field of displayFields) {
6153
6230
  if (obj[field] !== undefined && obj[field] !== null) {
6154
6231
  const value = obj[field];
6155
- // Return the value if it's a string or number, otherwise stringify it
6232
+ // Return the value if it's a string or number
6156
6233
  if (typeof value === 'string' || typeof value === 'number') {
6157
6234
  return String(value);
6158
6235
  }
6236
+ // If the value is an object, show warning and recommend custom renderDisplay
6237
+ if (typeof value === 'object' &&
6238
+ !Array.isArray(value) &&
6239
+ !(value instanceof Date)) {
6240
+ 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 ? '...' : ''}`);
6241
+ // Still return the stringified value for now
6242
+ return JSON.stringify(value);
6243
+ }
6159
6244
  }
6160
6245
  }
6161
6246
  // If no display field found, fall back to JSON.stringify
6162
6247
  return JSON.stringify(item);
6163
6248
  }
6249
+ // For strings that look like JSON, show warning and recommend custom renderDisplay
6250
+ if (typeof item === 'string' &&
6251
+ (item.trim().startsWith('{') || item.trim().startsWith('['))) {
6252
+ 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 ? '...' : ''}`);
6253
+ return item;
6254
+ }
6164
6255
  // For non-objects (primitives, arrays, dates), use JSON.stringify
6165
6256
  return JSON.stringify(item);
6166
6257
  };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
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, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Checkbox as Checkbox$1, Icon, VStack, Heading, EmptyState as EmptyState$2, List, Table as Table$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Tag as Tag$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, useCombobox, Show, Skeleton, NumberInput, RadioCard, CheckboxGroup, Textarea as Textarea$1, InputGroup as InputGroup$1, Select, Center, Stack } 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, CheckboxCard as CheckboxCard$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, Tooltip as Tooltip$1, Group, InputElement, Tag as Tag$1, Checkbox as Checkbox$1, Icon, VStack, Heading, EmptyState as EmptyState$2, List, Table as Table$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Clipboard, Badge, Link, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, useCombobox, Show, Skeleton, NumberInput, RadioCard, CheckboxGroup, Textarea as Textarea$1, InputGroup as InputGroup$1, Select, Center, Stack } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
5
  import { createContext, useContext, useState, useMemo, useCallback, useEffect, useRef } from 'react';
@@ -3178,52 +3178,55 @@ const TableSelector = () => {
3178
3178
  }, "aria-label": 'reset selection', children: jsx(MdClear, {}) }))] }));
3179
3179
  };
3180
3180
 
3181
+ const Tag = React.forwardRef(function Tag(props, ref) {
3182
+ const { startElement, endElement, onClose, closable = !!onClose, children, ...rest } = props;
3183
+ return (jsxs(Tag$1.Root, { ref: ref, ...rest, children: [startElement && (jsx(Tag$1.StartElement, { children: startElement })), jsx(Tag$1.Label, { children: children }), endElement && (jsx(Tag$1.EndElement, { children: endElement })), closable && (jsx(Tag$1.EndElement, { children: jsx(Tag$1.CloseTrigger, { onClick: onClose }) }))] }));
3184
+ });
3185
+
3181
3186
  const Checkbox = React.forwardRef(function Checkbox(props, ref) {
3182
3187
  const { icon, children, inputProps, rootRef, ...rest } = props;
3183
3188
  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
3189
  });
3185
3190
 
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();
3191
+ const ColumnFilterMenu = ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, open: controlledOpen, onOpenChange, }) => {
3206
3192
  const [searchTerm, setSearchTerm] = useState('');
3207
3193
  const debouncedSearchTerm = useDebounce(searchTerm, 300);
3208
- const [isOpen, setIsOpen] = useState(false);
3209
- const [pendingFilterValue, setPendingFilterValue] = useState(undefined);
3210
- const debouncedFilterValue = useDebounce(pendingFilterValue, 300);
3211
- const lastAppliedValueRef = useRef('__INITIAL__');
3212
- const column = table.getColumn(columnId);
3213
- const currentFilterValue = column?.getFilterValue();
3194
+ const [internalOpen, setInternalOpen] = useState(false);
3195
+ // Use controlled open state if provided, otherwise use internal state
3196
+ const isOpen = controlledOpen !== undefined ? controlledOpen : internalOpen;
3197
+ const handleOpenChange = (details) => {
3198
+ if (onOpenChange) {
3199
+ onOpenChange(details.open);
3200
+ }
3201
+ else {
3202
+ setInternalOpen(details.open);
3203
+ }
3204
+ };
3205
+ const [internalValue, setInternalValue] = useState(undefined);
3206
+ // Use controlled value if provided, otherwise use internal state
3207
+ const currentFilterValue = controlledValue !== undefined ? controlledValue : internalValue;
3214
3208
  const isArrayFilter = filterVariant === 'tag';
3215
- // Apply debounced filter value to column
3209
+ // Default labels
3210
+ const defaultLabels = {
3211
+ filterByLabel: 'Filter by',
3212
+ filterLabelsPlaceholder: 'Filter labels',
3213
+ noFiltersMatchText: 'No filters match your search',
3214
+ };
3215
+ const finalLabels = { ...defaultLabels, ...labels };
3216
+ // Reset search term when menu closes
3216
3217
  useEffect(() => {
3217
- const currentKey = JSON.stringify(debouncedFilterValue);
3218
- // Only apply if the value has changed from what we last applied
3219
- if (currentKey !== lastAppliedValueRef.current) {
3220
- column?.setFilterValue(debouncedFilterValue);
3221
- lastAppliedValueRef.current = currentKey;
3222
- }
3223
- }, [debouncedFilterValue, column]);
3224
- // Debounced filter update function
3225
- const debouncedSetFilterValue = (value) => {
3226
- setPendingFilterValue(value);
3218
+ if (!isOpen) {
3219
+ setSearchTerm('');
3220
+ }
3221
+ }, [isOpen]);
3222
+ // Filter update function
3223
+ const setFilterValue = (value) => {
3224
+ if (onChange) {
3225
+ onChange(value);
3226
+ }
3227
+ else {
3228
+ setInternalValue(value);
3229
+ }
3227
3230
  };
3228
3231
  // Get active count for this column
3229
3232
  const activeCount = useMemo(() => {
@@ -3242,9 +3245,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3242
3245
  const searchLower = debouncedSearchTerm.toLowerCase();
3243
3246
  return filterOptions.filter((option) => option.label.toLowerCase().includes(searchLower));
3244
3247
  }, [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: {
3248
+ return (jsxs(MenuRoot, { open: isOpen, onOpenChange: handleOpenChange, 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
3249
  '&::-webkit-scrollbar': {
3249
3250
  width: '8px',
3250
3251
  },
@@ -3258,7 +3259,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3258
3259
  '&::-webkit-scrollbar-thumb:hover': {
3259
3260
  background: 'var(--chakra-colors-border-subtle)',
3260
3261
  },
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) => {
3262
+ }, 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
3263
  const isActive = isArrayFilter
3263
3264
  ? currentFilterValue?.includes(option.value) ?? false
3264
3265
  : currentFilterValue === option.value;
@@ -3270,29 +3271,26 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3270
3271
  // Remove from filter
3271
3272
  const newArray = currentArray.filter((v) => v !== option.value);
3272
3273
  if (newArray.length === 0) {
3273
- debouncedSetFilterValue(undefined);
3274
+ setFilterValue(undefined);
3274
3275
  }
3275
3276
  else {
3276
- debouncedSetFilterValue(newArray);
3277
+ setFilterValue(newArray);
3277
3278
  }
3278
3279
  }
3279
3280
  else {
3280
3281
  // Add to filter
3281
3282
  if (!currentArray.includes(option.value)) {
3282
- debouncedSetFilterValue([
3283
- ...currentArray,
3284
- option.value,
3285
- ]);
3283
+ setFilterValue([...currentArray, option.value]);
3286
3284
  }
3287
3285
  }
3288
3286
  }
3289
3287
  else {
3290
3288
  // Handle single value filters (select variant)
3291
3289
  if (isActive) {
3292
- debouncedSetFilterValue(undefined);
3290
+ setFilterValue(undefined);
3293
3291
  }
3294
3292
  else {
3295
- debouncedSetFilterValue(option.value);
3293
+ setFilterValue(option.value);
3296
3294
  }
3297
3295
  }
3298
3296
  };
@@ -3307,7 +3305,7 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3307
3305
  if (details.checked) {
3308
3306
  // Add to filter
3309
3307
  if (!currentArray.includes(option.value)) {
3310
- debouncedSetFilterValue([
3308
+ setFilterValue([
3311
3309
  ...currentArray,
3312
3310
  option.value,
3313
3311
  ]);
@@ -3317,27 +3315,46 @@ const ColumnFilterMenu = ({ columnId, displayName, filterOptions, filterVariant,
3317
3315
  // Remove from filter
3318
3316
  const newArray = currentArray.filter((v) => v !== option.value);
3319
3317
  if (newArray.length === 0) {
3320
- debouncedSetFilterValue(undefined);
3318
+ setFilterValue(undefined);
3321
3319
  }
3322
3320
  else {
3323
- debouncedSetFilterValue(newArray);
3321
+ setFilterValue(newArray);
3324
3322
  }
3325
3323
  }
3326
3324
  }
3327
3325
  else {
3328
3326
  // Handle single value filters (select variant)
3329
3327
  if (details.checked) {
3330
- debouncedSetFilterValue(option.value);
3328
+ setFilterValue(option.value);
3331
3329
  }
3332
3330
  else {
3333
- debouncedSetFilterValue(undefined);
3331
+ setFilterValue(undefined);
3334
3332
  }
3335
3333
  }
3336
3334
  } }) }), 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
3335
  })) }) })] }) })] }));
3338
3336
  };
3337
+
3338
+ // Generate a color based on column id for visual distinction
3339
+ const getColorForColumn = (id) => {
3340
+ const colors = [
3341
+ 'blue',
3342
+ 'green',
3343
+ 'purple',
3344
+ 'orange',
3345
+ 'pink',
3346
+ 'cyan',
3347
+ 'teal',
3348
+ 'red',
3349
+ ];
3350
+ let hash = 0;
3351
+ for (let i = 0; i < id.length; i++) {
3352
+ hash = id.charCodeAt(i) + ((hash << 5) - hash);
3353
+ }
3354
+ return colors[Math.abs(hash) % colors.length];
3355
+ };
3339
3356
  const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
3340
- const { table } = useDataTableContext();
3357
+ const { table, tableLabel } = useDataTableContext();
3341
3358
  // Get columns from filterTagsOptions
3342
3359
  const columnsWithFilters = useMemo(() => {
3343
3360
  if (filterTagsOptions.length === 0) {
@@ -3352,26 +3369,91 @@ const TableFilterTags = ({ filterTagsOptions = [], } = {}) => {
3352
3369
  const meta = column.columnDef.meta;
3353
3370
  const displayName = meta?.displayName ?? column.id;
3354
3371
  const filterVariant = meta?.filterVariant;
3372
+ if (!column) {
3373
+ return null;
3374
+ }
3355
3375
  return {
3356
3376
  columnId: option.column,
3357
3377
  displayName,
3358
3378
  filterOptions: option.options,
3359
- filterVariant: filterVariant === 'tag' ? 'tag' : 'select',
3379
+ filterVariant: (filterVariant === 'tag' ? 'tag' : 'select'),
3360
3380
  colorPalette: getColorForColumn(option.column),
3381
+ column,
3361
3382
  };
3362
3383
  })
3363
- .filter((col) => col !== null);
3384
+ .filter((col) => col !== null && col.column !== null && col.column !== undefined);
3364
3385
  }, [table, filterTagsOptions]);
3365
3386
  if (columnsWithFilters.length === 0) {
3366
3387
  return null;
3367
3388
  }
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))) }));
3389
+ return (jsx(Flex, { gap: 2, flexWrap: "wrap", children: columnsWithFilters.map((column) => {
3390
+ const filterValue = column.column.getFilterValue();
3391
+ return (jsx(ColumnFilterMenu, { displayName: column.displayName, filterOptions: column.filterOptions, filterVariant: column.filterVariant, colorPalette: column.colorPalette, value: filterValue, onChange: (value) => column.column.setFilterValue(value), labels: {
3392
+ filterByLabel: tableLabel.filterByLabel,
3393
+ filterLabelsPlaceholder: tableLabel.filterLabelsPlaceholder,
3394
+ noFiltersMatchText: tableLabel.noFiltersMatchText,
3395
+ } }, column.columnId));
3396
+ }) }));
3369
3397
  };
3370
3398
 
3371
- 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
- const { tableLabel, table } = useDataTableContext();
3399
+ const TableControls = ({ fitTableWidth = false, fitTableHeight = false, children = jsx(Fragment, {}), showGlobalFilter = false, showFilter = false, showReload = false, showPagination = true, showPageSizeControl = true, showPageCountText = true, showView = true, filterTagsOptions = [], extraItems = jsx(Fragment, {}), loading = false, hasError = false, gridProps = {}, }) => {
3400
+ const { tableLabel, table, columnFilters, setColumnFilters } = useDataTableContext();
3373
3401
  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, {}) })] }))] }));
3402
+ // Get applied filters with display information
3403
+ const appliedFilters = useMemo(() => {
3404
+ return columnFilters
3405
+ .map((filter) => {
3406
+ const column = table.getColumn(filter.id);
3407
+ if (!column)
3408
+ return null;
3409
+ const meta = column.columnDef.meta;
3410
+ const displayName = meta?.displayName ?? filter.id;
3411
+ const filterValue = filter.value;
3412
+ // Handle array values (tag filters)
3413
+ if (Array.isArray(filterValue)) {
3414
+ return {
3415
+ columnId: filter.id,
3416
+ displayName,
3417
+ values: filterValue,
3418
+ isArray: true,
3419
+ };
3420
+ }
3421
+ // Handle single values (select filters)
3422
+ if (filterValue !== undefined &&
3423
+ filterValue !== null &&
3424
+ filterValue !== '') {
3425
+ return {
3426
+ columnId: filter.id,
3427
+ displayName,
3428
+ value: String(filterValue),
3429
+ isArray: false,
3430
+ };
3431
+ }
3432
+ return null;
3433
+ })
3434
+ .filter((filter) => filter !== null);
3435
+ }, [columnFilters, table]);
3436
+ const handleRemoveFilter = (columnId) => {
3437
+ setColumnFilters(columnFilters.filter((f) => f.id !== columnId));
3438
+ };
3439
+ return (jsxs(Grid, { templateRows: 'auto 1fr', width: fitTableWidth ? 'fit-content' : '100%', height: fitTableHeight ? 'fit-content' : '100%', gap: '0.5rem', p: 1, ...gridProps, children: [jsx(Flex, { flexFlow: 'column', gap: 2, children: jsxs(Flex, { justifyContent: 'space-between', children: [jsxs(Flex, { gap: 2, alignItems: 'center', flexWrap: 'wrap', children: [showView && jsx(ViewDialog, { icon: jsx(MdOutlineViewColumn, {}) }), appliedFilters.length > 0 && (jsx(Flex, { gap: 1.5, alignItems: 'center', flexWrap: 'wrap', children: appliedFilters.map((filter) => {
3440
+ if (filter.isArray) {
3441
+ return filter.values.map((value, index) => (jsxs(Tag, { size: "sm", colorPalette: "blue", onClose: () => {
3442
+ const column = table.getColumn(filter.columnId);
3443
+ if (column) {
3444
+ const currentValue = column.getFilterValue() ?? [];
3445
+ const newValue = currentValue.filter((v) => v !== value);
3446
+ if (newValue.length === 0) {
3447
+ handleRemoveFilter(filter.columnId);
3448
+ }
3449
+ else {
3450
+ column.setFilterValue(newValue);
3451
+ }
3452
+ }
3453
+ }, children: [filter.displayName, ": ", value] }, `${filter.columnId}-${value}-${index}`)));
3454
+ }
3455
+ return (jsxs(Tag, { size: "sm", colorPalette: "blue", onClose: () => handleRemoveFilter(filter.columnId), children: [filter.displayName, ": ", filter.value] }, filter.columnId));
3456
+ }) }))] }), 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, {}), filterTagsOptions.length > 0 && (jsx(TableFilterTags, { filterTagsOptions: filterTagsOptions })), showFilter && jsx(FilterDialog, {}), showReload && jsx(ReloadButton, {}), extraItems] })] }) }), 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
3457
  };
3376
3458
 
3377
3459
  const EmptyState$1 = React.forwardRef(function EmptyState(props, ref) {
@@ -3939,11 +4021,6 @@ label, containerProps = {}, textProps = {}, children, }) => {
3939
4021
  return (jsx(Box, { textOverflow: "ellipsis", whiteSpace: "nowrap", wordBreak: "break-all", overflow: "auto", display: "flex", alignItems: "center", justifyContent: alignEnd ? 'flex-end' : undefined, height: "100%", textAlign: alignEnd ? 'right' : undefined, children: jsx(RenderValue, { text: displayValue, href: href, onClick: onClick, isCopyable: isCopyable, isBadge: isBadge, badgeColor: badgeColor, colorPalette: colorPalette, globalFilter: globalFilter, alignEnd: alignEnd }) }));
3940
4022
  };
3941
4023
 
3942
- const Tag = React.forwardRef(function Tag(props, ref) {
3943
- const { startElement, endElement, onClose, closable = !!onClose, children, ...rest } = props;
3944
- return (jsxs(Tag$1.Root, { ref: ref, ...rest, children: [startElement && (jsx(Tag$1.StartElement, { children: startElement })), jsx(Tag$1.Label, { children: children }), endElement && (jsx(Tag$1.EndElement, { children: endElement })), closable && (jsx(Tag$1.EndElement, { children: jsx(Tag$1.CloseTrigger, { onClick: onClose }) }))] }));
3945
- });
3946
-
3947
4024
  const CardHeader = ({ row, imageColumnId = undefined, titleColumnId = undefined, tagColumnId = undefined, tagIcon = undefined, showTag = true, imageProps = {}, }) => {
3948
4025
  if (!!row.original === false) {
3949
4026
  return jsx(Fragment, {});
@@ -6132,15 +6209,29 @@ const defaultRenderDisplay = (item) => {
6132
6209
  for (const field of displayFields) {
6133
6210
  if (obj[field] !== undefined && obj[field] !== null) {
6134
6211
  const value = obj[field];
6135
- // Return the value if it's a string or number, otherwise stringify it
6212
+ // Return the value if it's a string or number
6136
6213
  if (typeof value === 'string' || typeof value === 'number') {
6137
6214
  return String(value);
6138
6215
  }
6216
+ // If the value is an object, show warning and recommend custom renderDisplay
6217
+ if (typeof value === 'object' &&
6218
+ !Array.isArray(value) &&
6219
+ !(value instanceof Date)) {
6220
+ 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 ? '...' : ''}`);
6221
+ // Still return the stringified value for now
6222
+ return JSON.stringify(value);
6223
+ }
6139
6224
  }
6140
6225
  }
6141
6226
  // If no display field found, fall back to JSON.stringify
6142
6227
  return JSON.stringify(item);
6143
6228
  }
6229
+ // For strings that look like JSON, show warning and recommend custom renderDisplay
6230
+ if (typeof item === 'string' &&
6231
+ (item.trim().startsWith('{') || item.trim().startsWith('['))) {
6232
+ 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 ? '...' : ''}`);
6233
+ return item;
6234
+ }
6144
6235
  // For non-objects (primitives, arrays, dates), use JSON.stringify
6145
6236
  return JSON.stringify(item);
6146
6237
  };
@@ -0,0 +1,20 @@
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
+ open?: boolean;
18
+ onOpenChange?: (open: boolean) => void;
19
+ }
20
+ export declare const ColumnFilterMenu: ({ displayName, filterOptions, filterVariant, colorPalette, value: controlledValue, onChange, labels, open: controlledOpen, onOpenChange, }: ColumnFilterMenuProps) => import("react/jsx-runtime").JSX.Element;
@@ -7,8 +7,6 @@ export interface TableControlsProps {
7
7
  children?: ReactNode;
8
8
  showGlobalFilter?: boolean;
9
9
  showFilter?: boolean;
10
- showFilterName?: boolean;
11
- showFilterTags?: boolean;
12
10
  showReload?: boolean;
13
11
  showPagination?: boolean;
14
12
  showPageSizeControl?: boolean;
@@ -26,4 +24,4 @@ export interface TableControlsProps {
26
24
  hasError?: boolean;
27
25
  gridProps?: GridProps;
28
26
  }
29
- export declare const TableControls: ({ fitTableWidth, fitTableHeight, children, showGlobalFilter, showFilter, showFilterName, showFilterTags, showReload, showPagination, showPageSizeControl, showPageCountText, showView, filterTagsOptions, extraItems, loading, hasError, gridProps, }: TableControlsProps) => import("react/jsx-runtime").JSX.Element;
27
+ export declare const TableControls: ({ fitTableWidth, fitTableHeight, children, showGlobalFilter, showFilter, showReload, showPagination, showPageSizeControl, showPageCountText, showView, filterTagsOptions, extraItems, loading, hasError, gridProps, }: TableControlsProps) => 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.15",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",