@bsol-oss/react-datatable5 12.0.0-beta.84 → 12.0.0-beta.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -308,4 +308,5 @@ npm run storybook
308
308
 
309
309
  ```
310
310
  npm version prerelease --preid=beta
311
+ npm publish
311
312
  ```
package/dist/index.js CHANGED
@@ -575,7 +575,7 @@ const PaginationItems = (props) => {
575
575
  return page.type === "ellipsis" ? (jsxRuntime.jsx(PaginationEllipsis, { index: index, ...props }, index)) : (jsxRuntime.jsx(PaginationItem, { type: "page", value: page.value, ...props }, index));
576
576
  }) }));
577
577
  };
578
- const PaginationPageText = React__namespace.forwardRef(function PaginationPageText(props, ref) {
578
+ React__namespace.forwardRef(function PaginationPageText(props, ref) {
579
579
  const { format = "compact", ...rest } = props;
580
580
  const { page, totalPages, pageRange, count } = react.usePaginationContext();
581
581
  const content = React__namespace.useMemo(() => {
@@ -4176,16 +4176,6 @@ const DateRangePicker = ({ column, schema, prefix, }) => {
4176
4176
  }, monthsToDisplay: 2 })] }) })] }) }));
4177
4177
  };
4178
4178
 
4179
- function filterArray(array, searchTerm) {
4180
- // Convert the search term to lower case for case-insensitive comparison
4181
- const lowerCaseSearchTerm = searchTerm.toLowerCase();
4182
- // Use the filter method to return an array of matching items
4183
- return array.filter((item) => {
4184
- // Convert each item to a string and check if it includes the search term
4185
- return item.toString().toLowerCase().includes(lowerCaseSearchTerm);
4186
- });
4187
- }
4188
-
4189
4179
  const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLimit = false, }) => {
4190
4180
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
4191
4181
  const { enumPickerLabels } = useSchemaContext();
@@ -4193,89 +4183,74 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4193
4183
  const { required, variant } = schema;
4194
4184
  const isRequired = required?.some((columnId) => columnId === column);
4195
4185
  const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
4196
- const [searchText, setSearchText] = React.useState();
4197
- const [limit, setLimit] = React.useState(10);
4198
- const [openSearchResult, setOpenSearchResult] = React.useState();
4199
- const ref = React.useRef(null);
4200
4186
  const colLabel = formI18n.colLabel;
4201
4187
  const watchEnum = watch(colLabel);
4202
4188
  const watchEnums = (watch(colLabel) ?? []);
4203
4189
  const dataList = schema.enum ?? [];
4204
- const count = schema.enum?.length ?? 0;
4205
- const isDirty = (searchText?.length ?? 0) > 0;
4206
- const onSearchChange = async (event) => {
4207
- setSearchText(event.target.value);
4208
- setLimit(10);
4190
+ // Current value for combobox (array format)
4191
+ const currentValue = isMultiple
4192
+ ? watchEnums.filter((val) => val != null && val !== '')
4193
+ : watchEnum
4194
+ ? [watchEnum]
4195
+ : [];
4196
+ // Transform enum data for combobox collection
4197
+ const comboboxItems = React.useMemo(() => {
4198
+ return dataList.map((item) => ({
4199
+ label: !!renderDisplay === true
4200
+ ? String(renderDisplay(item))
4201
+ : formI18n.t(item),
4202
+ value: item,
4203
+ }));
4204
+ }, [dataList, renderDisplay, formI18n]);
4205
+ // Use filter hook for combobox
4206
+ const { contains } = react.useFilter({ sensitivity: 'base' });
4207
+ // Create collection for combobox
4208
+ const { collection, filter } = react.useListCollection({
4209
+ initialItems: comboboxItems,
4210
+ itemToString: (item) => item.label,
4211
+ itemToValue: (item) => item.value,
4212
+ filter: contains,
4213
+ });
4214
+ // Handle input value change (search)
4215
+ const handleInputValueChange = (details) => {
4216
+ filter(details.inputValue);
4217
+ };
4218
+ // Handle value change
4219
+ const handleValueChange = (details) => {
4220
+ if (isMultiple) {
4221
+ setValue(colLabel, details.value);
4222
+ }
4223
+ else {
4224
+ setValue(colLabel, details.value[0] || '');
4225
+ }
4209
4226
  };
4210
4227
  if (variant === 'radio') {
4211
4228
  return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4212
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsx(react.RadioGroup.Root, { defaultValue: "1", children: jsxRuntime.jsx(react.HStack, { gap: "6", children: filterArray(dataList, searchText ?? '').map((item) => {
4213
- return (jsxRuntime.jsxs(react.RadioGroup.Item, { onClick: () => {
4214
- if (!isMultiple) {
4215
- setOpenSearchResult(false);
4216
- setValue(colLabel, item);
4217
- return;
4218
- }
4219
- const newSet = new Set([...(watchEnums ?? []), item]);
4220
- setValue(colLabel, [...newSet]);
4221
- }, value: item, children: [jsxRuntime.jsx(react.RadioGroup.ItemHiddenInput, {}), jsxRuntime.jsx(react.RadioGroup.ItemIndicator, {}), jsxRuntime.jsx(react.RadioGroup.ItemText, { children: !!renderDisplay === true
4229
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsx(react.RadioGroup.Root, { value: !isMultiple ? watchEnum : undefined, onValueChange: (details) => {
4230
+ if (!isMultiple) {
4231
+ setValue(colLabel, details.value);
4232
+ }
4233
+ }, children: jsxRuntime.jsx(react.HStack, { gap: "6", children: dataList.map((item) => {
4234
+ return (jsxRuntime.jsxs(react.RadioGroup.Item, { value: item, children: [jsxRuntime.jsx(react.RadioGroup.ItemHiddenInput, {}), jsxRuntime.jsx(react.RadioGroup.ItemIndicator, {}), jsxRuntime.jsx(react.RadioGroup.ItemText, { children: !!renderDisplay === true
4222
4235
  ? renderDisplay(item)
4223
4236
  : formI18n.t(item) })] }, `${colLabel}-${item}`));
4224
4237
  }) }) }) }));
4225
4238
  }
4226
4239
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4227
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && (jsxRuntime.jsxs(react.Flex, { flexFlow: 'wrap', gap: 1, children: [watchEnums.map((enumValue) => {
4228
- const item = enumValue;
4229
- if (!!item === false) {
4230
- return jsxRuntime.jsx(jsxRuntime.Fragment, {});
4231
- }
4232
- return (jsxRuntime.jsx(Tag, { size: "lg", closable: true, onClick: () => {
4233
- setValue(column, watchEnums.filter((id) => id != item));
4234
- }, children: !!renderDisplay === true
4235
- ? renderDisplay(item)
4236
- : formI18n.t(item) }, item));
4237
- }), jsxRuntime.jsx(Tag, { size: "lg", cursor: 'pointer', onClick: () => {
4238
- setOpenSearchResult(true);
4239
- }, children: enumPickerLabels?.addMore ?? formI18n.t('add_more') }, `${colLabel}-add-more-tag`)] })), !isMultiple && (jsxRuntime.jsx(Button, { variant: 'outline', onClick: () => {
4240
- setOpenSearchResult(true);
4241
- }, justifyContent: 'start', children: !!watchEnum === false ? '' : formI18n.t(watchEnum ?? 'null') })), jsxRuntime.jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: 'bottom-start' }, children: [jsxRuntime.jsx(PopoverTrigger, {}), jsxRuntime.jsx(PopoverContent, { portalled: false, children: jsxRuntime.jsxs(PopoverBody, { display: 'grid', gap: 1, children: [jsxRuntime.jsx(react.Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search'), onChange: (event) => {
4242
- onSearchChange(event);
4243
- setOpenSearchResult(true);
4244
- }, autoComplete: "off", ref: ref }), jsxRuntime.jsx(PopoverTitle, {}), showTotalAndLimit && (jsxRuntime.jsx(react.Text, { children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${count}, ${enumPickerLabels?.showing ?? formI18n.t('showing')} ${limit}` })), jsxRuntime.jsxs(react.Grid, { overflow: 'auto', maxHeight: '20rem', children: [jsxRuntime.jsx(react.Flex, { flexFlow: 'column wrap', children: dataList
4245
- .filter((item) => {
4246
- const searchTerm = (searchText || '').toLowerCase();
4247
- if (!searchTerm)
4248
- return true;
4249
- // Check if the original enum value contains the search text
4250
- const enumValueMatch = item
4251
- .toLowerCase()
4252
- .includes(searchTerm);
4253
- // Check if the display value (translation) contains the search text
4254
- const displayValue = !!renderDisplay === true
4255
- ? renderDisplay(item)
4256
- : formI18n.t(item);
4257
- // Convert to string and check if it includes the search term
4258
- const displayValueString = String(displayValue).toLowerCase();
4259
- const displayValueMatch = displayValueString.includes(searchTerm);
4260
- return enumValueMatch || displayValueMatch;
4261
- })
4262
- .map((item) => {
4263
- const selected = isMultiple
4264
- ? watchEnums.some((enumValue) => item === enumValue)
4265
- : watchEnum == item;
4266
- return (jsxRuntime.jsx(react.Box, { cursor: 'pointer', onClick: () => {
4267
- if (!isMultiple) {
4268
- setOpenSearchResult(false);
4269
- setValue(colLabel, item);
4270
- return;
4271
- }
4272
- const newSet = new Set([...(watchEnums ?? []), item]);
4273
- setValue(colLabel, [...newSet]);
4274
- }, ...(selected ? { color: 'colorPalette.400/50' } : {}), children: !!renderDisplay === true
4275
- ? renderDisplay(item)
4276
- : formI18n.t(item) }, `${colLabel}-${item}`));
4277
- }) }), isDirty && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: dataList.length <= 0 && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: enumPickerLabels?.emptySearchResult ??
4278
- formI18n.t('empty_search_result') })) }))] })] }) })] })] }));
4240
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((enumValue) => {
4241
+ if (!enumValue) {
4242
+ return null;
4243
+ }
4244
+ return (jsxRuntime.jsx(Tag, { size: "lg", closable: true, onClick: () => {
4245
+ const newValue = currentValue.filter((val) => val !== enumValue);
4246
+ setValue(colLabel, newValue);
4247
+ }, children: !!renderDisplay === true
4248
+ ? renderDisplay(enumValue)
4249
+ : formI18n.t(enumValue) }, enumValue));
4250
+ }) })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
4251
+ setValue(colLabel, '');
4252
+ } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsxs(react.Combobox.Content, { children: [showTotalAndLimit && (jsxRuntime.jsx(react.Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${collection.items.length}` })), collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ??
4253
+ formI18n.t('empty_search_result') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value))) }))] }) }) })] })] }));
4279
4254
  };
4280
4255
 
4281
4256
  function isEnteringWindow(_ref) {
@@ -4667,9 +4642,7 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
4667
4642
  : files;
4668
4643
  const handleFileClick = (file) => {
4669
4644
  if (multiple) {
4670
- const currentSelection = Array.isArray(selectedFile)
4671
- ? selectedFile
4672
- : [];
4645
+ const currentSelection = Array.isArray(selectedFile) ? selectedFile : [];
4673
4646
  const isAlreadySelected = currentSelection.some((f) => f.id === file.id);
4674
4647
  const newSelection = isAlreadySelected
4675
4648
  ? currentSelection.filter((f) => f.id !== file.id)
@@ -5036,15 +5009,6 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5036
5009
  }) })] }));
5037
5010
  };
5038
5011
 
5039
- const ToggleTip = React__namespace.forwardRef(function ToggleTip(props, ref) {
5040
- const { showArrow, children, portalled = true, content, portalRef, ...rest } = props;
5041
- 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] }) }) })] }));
5042
- });
5043
- const InfoTip = React__namespace.forwardRef(function InfoTip(props, ref) {
5044
- const { children, ...rest } = props;
5045
- return (jsxRuntime.jsx(ToggleTip, { content: children, ...rest, ref: ref, children: jsxRuntime.jsx(react.IconButton, { variant: "ghost", "aria-label": "info", size: "2xs", colorPalette: "colorPalette", children: jsxRuntime.jsx(hi.HiOutlineInformationCircle, {}) }) }));
5046
- });
5047
-
5048
5012
  const getTableData = async ({ serverUrl, in_table, searching = "", where = [], limit = 10, offset = 0, }) => {
5049
5013
  if (serverUrl === undefined || serverUrl.length == 0) {
5050
5014
  throw new Error("The serverUrl is missing");
@@ -5081,10 +5045,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5081
5045
  const isRequired = required?.some((columnId) => columnId === column);
5082
5046
  const { table, column: column_ref, display_column, customQueryFn, } = foreign_key;
5083
5047
  const [searchText, setSearchText] = React.useState('');
5084
- const [limit, setLimit] = React.useState(10);
5085
- const [openSearchResult, setOpenSearchResult] = React.useState();
5086
- const [page, setPage] = React.useState(0);
5087
- const ref = React.useRef(null);
5048
+ const [limit] = React.useState(50); // Increased limit for combobox
5088
5049
  const colLabel = formI18n.colLabel;
5089
5050
  const watchedValue = watch(colLabel);
5090
5051
  const watchId = !isMultiple ? watchedValue : undefined;
@@ -5099,22 +5060,25 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5099
5060
  : [];
5100
5061
  // Use watched values if they exist (including empty string for single select),
5101
5062
  // otherwise fall back to initial values from getValues()
5102
- // This ensures the query can trigger on mount with initial values
5103
- // For single: use watchId if it's not undefined/null, otherwise use initialId
5104
- // For multiple: use watchIds if watchedValue is defined, otherwise use initialIds
5105
5063
  const currentId = watchId !== undefined && watchId !== null ? watchId : initialId;
5106
5064
  const currentIds = watchedValue !== undefined && watchedValue !== null && isMultiple
5107
5065
  ? watchIds
5108
5066
  : initialIds;
5109
- // Query for search results
5067
+ // Current value for combobox (array format)
5068
+ const currentValue = isMultiple
5069
+ ? currentIds.filter((id) => id != null && id !== '')
5070
+ : currentId
5071
+ ? [currentId]
5072
+ : [];
5073
+ // Query for search results (async loading)
5110
5074
  const query = reactQuery.useQuery({
5111
- queryKey: [`idpicker`, { column, searchText, limit, page }],
5075
+ queryKey: [`idpicker`, { column, searchText, limit }],
5112
5076
  queryFn: async () => {
5113
5077
  if (customQueryFn) {
5114
5078
  const { data, idMap } = await customQueryFn({
5115
5079
  searching: searchText ?? '',
5116
5080
  limit: limit,
5117
- offset: page * limit,
5081
+ offset: 0,
5118
5082
  });
5119
5083
  setIdMap((state) => {
5120
5084
  return { ...state, ...idMap };
@@ -5126,7 +5090,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5126
5090
  searching: searchText ?? '',
5127
5091
  in_table: table,
5128
5092
  limit: limit,
5129
- offset: page * limit,
5093
+ offset: 0,
5130
5094
  });
5131
5095
  const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5132
5096
  return [
@@ -5141,12 +5105,11 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5141
5105
  });
5142
5106
  return data;
5143
5107
  },
5144
- enabled: openSearchResult === true,
5108
+ enabled: true, // Always enabled for combobox
5145
5109
  staleTime: 300000,
5146
5110
  });
5147
5111
  // Query for currently selected items (to display them properly)
5148
- // Use currentId/currentIds in queryKey so it includes initial values and updates when watched values change
5149
- const queryDefault = reactQuery.useQuery({
5112
+ reactQuery.useQuery({
5150
5113
  queryKey: [
5151
5114
  `idpicker-default`,
5152
5115
  {
@@ -5156,11 +5119,9 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5156
5119
  },
5157
5120
  ],
5158
5121
  queryFn: async () => {
5159
- // Use current values (which include initial) for the query
5160
5122
  const queryId = currentId;
5161
5123
  const queryIds = currentIds;
5162
5124
  if (customQueryFn) {
5163
- // For customQueryFn, pass where clause to fetch specific IDs
5164
5125
  const { data, idMap } = await customQueryFn({
5165
5126
  searching: '',
5166
5127
  limit: isMultiple ? queryIds.length : 1,
@@ -5175,10 +5136,9 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5175
5136
  if (!queryId && (!queryIds || queryIds.length === 0)) {
5176
5137
  return { data: [] };
5177
5138
  }
5178
- const searchValue = isMultiple ? queryIds.join(',') : queryId;
5179
5139
  const data = await getTableData({
5180
5140
  serverUrl,
5181
- searching: searchValue,
5141
+ searching: '',
5182
5142
  in_table: table,
5183
5143
  where: [{ id: column_ref, value: isMultiple ? queryIds : queryId }],
5184
5144
  limit: isMultiple ? queryIds.length : 1,
@@ -5201,101 +5161,80 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5201
5161
  ? Array.isArray(currentIds) && currentIds.length > 0
5202
5162
  : !!currentId,
5203
5163
  });
5204
- // Effect to trigger initial data fetch when popover opens
5205
- React.useEffect(() => {
5206
- if (openSearchResult) {
5207
- // Reset search text when opening the popover
5208
- setSearchText('');
5209
- // Reset page to first page
5210
- setPage(0);
5211
- // Fetch initial data
5212
- query.refetch();
5213
- }
5214
- // eslint-disable-next-line react-hooks/exhaustive-deps
5215
- }, [openSearchResult]);
5216
- const onSearchChange = async (event) => {
5217
- setSearchText(event.target.value);
5218
- setPage(0);
5219
- query.refetch();
5220
- };
5221
- const handleLimitChange = (event) => {
5222
- const newLimit = Number(event.target.value);
5223
- setLimit(newLimit);
5224
- // Reset to first page when changing limit
5225
- setPage(0);
5226
- // Trigger a new search with the updated limit
5227
- query.refetch();
5228
- };
5229
5164
  const { isLoading, isFetching, data, isPending, isError } = query;
5230
5165
  const dataList = data?.data ?? [];
5231
- const count = data?.count ?? 0;
5232
- const getPickedValue = () => {
5233
- if (Object.keys(idMap).length <= 0) {
5234
- return '';
5235
- }
5236
- // Use currentId which includes initial values
5237
- const record = idMap[currentId];
5238
- if (record === undefined) {
5239
- return '';
5166
+ // Transform data for combobox collection
5167
+ const comboboxItems = React.useMemo(() => {
5168
+ return dataList.map((item) => ({
5169
+ label: !!renderDisplay === true
5170
+ ? String(renderDisplay(item))
5171
+ : String(item[display_column] ?? ''),
5172
+ value: String(item[column_ref]),
5173
+ raw: item,
5174
+ }));
5175
+ }, [dataList, display_column, column_ref, renderDisplay]);
5176
+ // Use filter hook for combobox
5177
+ const { contains } = react.useFilter({ sensitivity: 'base' });
5178
+ // Create collection for combobox
5179
+ const { collection, filter, set } = react.useListCollection({
5180
+ initialItems: comboboxItems,
5181
+ itemToString: (item) => item.label,
5182
+ itemToValue: (item) => item.value,
5183
+ filter: contains,
5184
+ });
5185
+ // Handle input value change (search)
5186
+ const handleInputValueChange = (details) => {
5187
+ setSearchText(details.inputValue);
5188
+ // Filter will be applied after data is fetched
5189
+ };
5190
+ // Handle value change
5191
+ const handleValueChange = (details) => {
5192
+ if (isMultiple) {
5193
+ setValue(colLabel, details.value);
5240
5194
  }
5241
- if (!!renderDisplay === true) {
5242
- return renderDisplay(record);
5195
+ else {
5196
+ setValue(colLabel, details.value[0] || '');
5243
5197
  }
5244
- return record[display_column];
5245
5198
  };
5199
+ // Debounce search to avoid too many API calls and update collection after data loads
5200
+ React.useEffect(() => {
5201
+ const timer = setTimeout(() => {
5202
+ if (searchText !== undefined) {
5203
+ query.refetch();
5204
+ }
5205
+ }, 300);
5206
+ return () => clearTimeout(timer);
5207
+ }, [searchText, query]);
5208
+ // Update collection and filter when data changes
5209
+ React.useEffect(() => {
5210
+ if (dataList.length > 0) {
5211
+ set(comboboxItems);
5212
+ // Apply filter to the collection
5213
+ if (searchText) {
5214
+ filter(searchText);
5215
+ }
5216
+ }
5217
+ }, [dataList, comboboxItems, set, filter, searchText]);
5246
5218
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5247
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && (jsxRuntime.jsxs(react.Flex, { flexFlow: 'wrap', gap: 1, children: [watchIds.map((id) => {
5248
- const item = idMap[id];
5249
- if (item === undefined) {
5250
- return (jsxRuntime.jsx(react.Text, { children: idPickerLabels?.undefined ?? formI18n.t('undefined') }, id));
5251
- }
5252
- return (jsxRuntime.jsx(Tag, { closable: true, onClick: () => {
5253
- setValue(colLabel, watchIds.filter((itemId) => itemId !== item[column_ref]));
5254
- }, children: !!renderDisplay === true
5255
- ? renderDisplay(item)
5256
- : item[display_column] }, id));
5257
- }), jsxRuntime.jsx(Tag, { cursor: 'pointer', onClick: () => {
5258
- setOpenSearchResult(true);
5259
- }, children: idPickerLabels?.addMore ?? formI18n.t('add_more') })] })), !isMultiple && (jsxRuntime.jsx(Button, { variant: 'outline', onClick: () => {
5260
- setOpenSearchResult(true);
5261
- }, justifyContent: 'start', children: queryDefault.isLoading ? jsxRuntime.jsx(react.Spinner, { size: "sm" }) : getPickedValue() })), jsxRuntime.jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: 'bottom-start', strategy: 'fixed' }, children: [jsxRuntime.jsx(PopoverTrigger, {}), jsxRuntime.jsx(PopoverContent, { portalled: false, children: jsxRuntime.jsxs(PopoverBody, { display: 'grid', gap: 1, children: [jsxRuntime.jsx(react.Input, { placeholder: idPickerLabels?.typeToSearch ?? formI18n.t('type_to_search'), onChange: onSearchChange, autoComplete: "off", ref: ref, value: searchText }), jsxRuntime.jsx(PopoverTitle, {}), openSearchResult && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [(isFetching || isLoading || isPending) && jsxRuntime.jsx(react.Spinner, {}), isError && (jsxRuntime.jsx(react.Icon, { color: 'red.400', children: jsxRuntime.jsx(bi.BiError, {}) })), jsxRuntime.jsxs(react.Flex, { justifyContent: "space-between", alignItems: "center", children: [jsxRuntime.jsxs(react.Flex, { alignItems: "center", gap: "2", children: [jsxRuntime.jsx(InfoTip, { children: `${idPickerLabels?.total ?? formI18n.t('total')} ${count}, ${idPickerLabels?.showing ?? formI18n.t('showing')} ${limit} ${idPickerLabels?.perPage ?? formI18n.t('per_page', { defaultValue: 'per page' })}` }), jsxRuntime.jsxs(react.Text, { fontSize: "sm", fontWeight: "bold", children: [count, jsxRuntime.jsxs(react.Text, { as: "span", fontSize: "xs", ml: "1", color: "gray.500", children: ["/", ' ', count > 0
5262
- ? `${page * limit + 1}-${Math.min((page + 1) * limit, count)}`
5263
- : '0'] })] })] }), jsxRuntime.jsx(react.Box, { children: jsxRuntime.jsxs("select", { value: limit, onChange: handleLimitChange, style: {
5264
- padding: '4px 8px',
5265
- borderRadius: '4px',
5266
- border: '1px solid #ccc',
5267
- fontSize: '14px',
5268
- }, children: [jsxRuntime.jsx("option", { value: "5", children: "5" }), jsxRuntime.jsx("option", { value: "10", children: "10" }), jsxRuntime.jsx("option", { value: "20", children: "20" }), jsxRuntime.jsx("option", { value: "30", children: "30" })] }) })] }), jsxRuntime.jsx(react.Grid, { overflowY: 'auto', children: dataList.length > 0 ? (jsxRuntime.jsx(react.Flex, { flexFlow: 'column wrap', gap: 1, children: dataList.map((item) => {
5269
- const selected = isMultiple
5270
- ? watchIds.some((id) => item[column_ref] === id)
5271
- : watchId === item[column_ref];
5272
- return (jsxRuntime.jsx(react.Box, { cursor: 'pointer', onClick: () => {
5273
- if (!isMultiple) {
5274
- setOpenSearchResult(false);
5275
- setValue(colLabel, item[column_ref]);
5276
- return;
5277
- }
5278
- // For multiple selection, don't add if already selected
5279
- if (selected)
5280
- return;
5281
- const newSet = new Set([
5282
- ...(watchIds ?? []),
5283
- item[column_ref],
5284
- ]);
5285
- setValue(colLabel, [...newSet]);
5286
- }, opacity: 0.7, _hover: { opacity: 1 }, ...(selected
5287
- ? {
5288
- color: 'colorPalette.400/50',
5289
- fontWeight: 'bold',
5290
- }
5291
- : {}), children: !!renderDisplay === true
5292
- ? renderDisplay(item)
5293
- : item[display_column] }, item[column_ref]));
5294
- }) })) : (jsxRuntime.jsx(react.Text, { children: searchText
5295
- ? idPickerLabels?.emptySearchResult ??
5296
- formI18n.t('empty_search_result')
5297
- : idPickerLabels?.initialResults ??
5298
- formI18n.t('initial_results') })) }), jsxRuntime.jsx(PaginationRoot, { justifySelf: 'center', count: count, pageSize: limit, defaultPage: 1, page: page + 1, onPageChange: (e) => setPage(e.page - 1), children: jsxRuntime.jsxs(react.HStack, { gap: "4", children: [jsxRuntime.jsx(PaginationPrevTrigger, {}), count > 0 && jsxRuntime.jsx(PaginationPageText, {}), jsxRuntime.jsx(PaginationNextTrigger, {})] }) })] }))] }) })] })] }));
5219
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((id) => {
5220
+ const item = idMap[id];
5221
+ if (item === undefined) {
5222
+ return (jsxRuntime.jsx(react.Text, { fontSize: "sm", children: idPickerLabels?.undefined ?? formI18n.t('undefined') }, id));
5223
+ }
5224
+ return (jsxRuntime.jsx(Tag, { closable: true, onClick: () => {
5225
+ const newValue = currentValue.filter((itemId) => itemId !== id);
5226
+ setValue(colLabel, newValue);
5227
+ }, children: !!renderDisplay === true
5228
+ ? renderDisplay(item)
5229
+ : item[display_column] }, id));
5230
+ }) })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", children: [jsxRuntime.jsxs(react.Combobox.Control, { children: [jsxRuntime.jsx(react.Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxRuntime.jsxs(react.Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && (jsxRuntime.jsx(react.Spinner, { size: "xs" })), isError && (jsxRuntime.jsx(react.Icon, { color: "fg.error", children: jsxRuntime.jsx(bi.BiError, {}) })), !isMultiple && currentValue.length > 0 && (jsxRuntime.jsx(react.Combobox.ClearTrigger, { onClick: () => {
5231
+ setValue(colLabel, '');
5232
+ } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isFetching || isLoading || isPending ? (jsxRuntime.jsxs(react.HStack, { p: 2, justify: "center", children: [jsxRuntime.jsx(react.Spinner, { size: "xs" }), jsxRuntime.jsx(react.Text, { fontSize: "sm", children: idPickerLabels?.loading ?? formI18n.t('loading') })] })) : isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.loadingFailed ??
5233
+ formI18n.t('loading_failed') })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
5234
+ ? idPickerLabels?.emptySearchResult ??
5235
+ formI18n.t('empty_search_result')
5236
+ : idPickerLabels?.initialResults ??
5237
+ formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value))) })) }) }) })] })] }));
5299
5238
  };
5300
5239
 
5301
5240
  const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Image, Alert, Field as Field$1, Popover, Tabs, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading, Skeleton } from '@chakra-ui/react';
2
+ import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading, Skeleton } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
- import React__default, { createContext, useContext, useState, useEffect, useRef, forwardRef } from 'react';
5
+ import React__default, { createContext, useContext, useState, useEffect, useRef, useMemo, forwardRef } from 'react';
6
6
  import { LuX, LuCheck, LuChevronRight, LuSearch, LuImage, LuFile } from 'react-icons/lu';
7
7
  import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineChecklist, MdClear, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdDateRange } from 'react-icons/md';
8
8
  import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
@@ -21,7 +21,7 @@ import { useQueryClient, useQuery } from '@tanstack/react-query';
21
21
  import { IoReload } from 'react-icons/io5';
22
22
  import { useDebounce } from '@uidotdev/usehooks';
23
23
  import { BsExclamationCircleFill, BsClock } from 'react-icons/bs';
24
- import { HiColorSwatch, HiOutlineInformationCircle } from 'react-icons/hi';
24
+ import { HiColorSwatch } from 'react-icons/hi';
25
25
  import { flexRender, createColumnHelper, makeStateUpdater, functionalUpdate, useReactTable, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getPaginationRowModel } from '@tanstack/react-table';
26
26
  import { GrAscend, GrDescend } from 'react-icons/gr';
27
27
  import { useTranslation } from 'react-i18next';
@@ -555,7 +555,7 @@ const PaginationItems = (props) => {
555
555
  return page.type === "ellipsis" ? (jsx(PaginationEllipsis, { index: index, ...props }, index)) : (jsx(PaginationItem, { type: "page", value: page.value, ...props }, index));
556
556
  }) }));
557
557
  };
558
- const PaginationPageText = React.forwardRef(function PaginationPageText(props, ref) {
558
+ React.forwardRef(function PaginationPageText(props, ref) {
559
559
  const { format = "compact", ...rest } = props;
560
560
  const { page, totalPages, pageRange, count } = usePaginationContext();
561
561
  const content = React.useMemo(() => {
@@ -4156,16 +4156,6 @@ const DateRangePicker = ({ column, schema, prefix, }) => {
4156
4156
  }, monthsToDisplay: 2 })] }) })] }) }));
4157
4157
  };
4158
4158
 
4159
- function filterArray(array, searchTerm) {
4160
- // Convert the search term to lower case for case-insensitive comparison
4161
- const lowerCaseSearchTerm = searchTerm.toLowerCase();
4162
- // Use the filter method to return an array of matching items
4163
- return array.filter((item) => {
4164
- // Convert each item to a string and check if it includes the search term
4165
- return item.toString().toLowerCase().includes(lowerCaseSearchTerm);
4166
- });
4167
- }
4168
-
4169
4159
  const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLimit = false, }) => {
4170
4160
  const { watch, formState: { errors }, setValue, } = useFormContext();
4171
4161
  const { enumPickerLabels } = useSchemaContext();
@@ -4173,89 +4163,74 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4173
4163
  const { required, variant } = schema;
4174
4164
  const isRequired = required?.some((columnId) => columnId === column);
4175
4165
  const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
4176
- const [searchText, setSearchText] = useState();
4177
- const [limit, setLimit] = useState(10);
4178
- const [openSearchResult, setOpenSearchResult] = useState();
4179
- const ref = useRef(null);
4180
4166
  const colLabel = formI18n.colLabel;
4181
4167
  const watchEnum = watch(colLabel);
4182
4168
  const watchEnums = (watch(colLabel) ?? []);
4183
4169
  const dataList = schema.enum ?? [];
4184
- const count = schema.enum?.length ?? 0;
4185
- const isDirty = (searchText?.length ?? 0) > 0;
4186
- const onSearchChange = async (event) => {
4187
- setSearchText(event.target.value);
4188
- setLimit(10);
4170
+ // Current value for combobox (array format)
4171
+ const currentValue = isMultiple
4172
+ ? watchEnums.filter((val) => val != null && val !== '')
4173
+ : watchEnum
4174
+ ? [watchEnum]
4175
+ : [];
4176
+ // Transform enum data for combobox collection
4177
+ const comboboxItems = useMemo(() => {
4178
+ return dataList.map((item) => ({
4179
+ label: !!renderDisplay === true
4180
+ ? String(renderDisplay(item))
4181
+ : formI18n.t(item),
4182
+ value: item,
4183
+ }));
4184
+ }, [dataList, renderDisplay, formI18n]);
4185
+ // Use filter hook for combobox
4186
+ const { contains } = useFilter({ sensitivity: 'base' });
4187
+ // Create collection for combobox
4188
+ const { collection, filter } = useListCollection({
4189
+ initialItems: comboboxItems,
4190
+ itemToString: (item) => item.label,
4191
+ itemToValue: (item) => item.value,
4192
+ filter: contains,
4193
+ });
4194
+ // Handle input value change (search)
4195
+ const handleInputValueChange = (details) => {
4196
+ filter(details.inputValue);
4197
+ };
4198
+ // Handle value change
4199
+ const handleValueChange = (details) => {
4200
+ if (isMultiple) {
4201
+ setValue(colLabel, details.value);
4202
+ }
4203
+ else {
4204
+ setValue(colLabel, details.value[0] || '');
4205
+ }
4189
4206
  };
4190
4207
  if (variant === 'radio') {
4191
4208
  return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4192
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(RadioGroup$1.Root, { defaultValue: "1", children: jsx(HStack, { gap: "6", children: filterArray(dataList, searchText ?? '').map((item) => {
4193
- return (jsxs(RadioGroup$1.Item, { onClick: () => {
4194
- if (!isMultiple) {
4195
- setOpenSearchResult(false);
4196
- setValue(colLabel, item);
4197
- return;
4198
- }
4199
- const newSet = new Set([...(watchEnums ?? []), item]);
4200
- setValue(colLabel, [...newSet]);
4201
- }, value: item, children: [jsx(RadioGroup$1.ItemHiddenInput, {}), jsx(RadioGroup$1.ItemIndicator, {}), jsx(RadioGroup$1.ItemText, { children: !!renderDisplay === true
4209
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(RadioGroup$1.Root, { value: !isMultiple ? watchEnum : undefined, onValueChange: (details) => {
4210
+ if (!isMultiple) {
4211
+ setValue(colLabel, details.value);
4212
+ }
4213
+ }, children: jsx(HStack, { gap: "6", children: dataList.map((item) => {
4214
+ return (jsxs(RadioGroup$1.Item, { value: item, children: [jsx(RadioGroup$1.ItemHiddenInput, {}), jsx(RadioGroup$1.ItemIndicator, {}), jsx(RadioGroup$1.ItemText, { children: !!renderDisplay === true
4202
4215
  ? renderDisplay(item)
4203
4216
  : formI18n.t(item) })] }, `${colLabel}-${item}`));
4204
4217
  }) }) }) }));
4205
4218
  }
4206
4219
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
4207
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && (jsxs(Flex, { flexFlow: 'wrap', gap: 1, children: [watchEnums.map((enumValue) => {
4208
- const item = enumValue;
4209
- if (!!item === false) {
4210
- return jsx(Fragment, {});
4211
- }
4212
- return (jsx(Tag, { size: "lg", closable: true, onClick: () => {
4213
- setValue(column, watchEnums.filter((id) => id != item));
4214
- }, children: !!renderDisplay === true
4215
- ? renderDisplay(item)
4216
- : formI18n.t(item) }, item));
4217
- }), jsx(Tag, { size: "lg", cursor: 'pointer', onClick: () => {
4218
- setOpenSearchResult(true);
4219
- }, children: enumPickerLabels?.addMore ?? formI18n.t('add_more') }, `${colLabel}-add-more-tag`)] })), !isMultiple && (jsx(Button, { variant: 'outline', onClick: () => {
4220
- setOpenSearchResult(true);
4221
- }, justifyContent: 'start', children: !!watchEnum === false ? '' : formI18n.t(watchEnum ?? 'null') })), jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: 'bottom-start' }, children: [jsx(PopoverTrigger, {}), jsx(PopoverContent, { portalled: false, children: jsxs(PopoverBody, { display: 'grid', gap: 1, children: [jsx(Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search'), onChange: (event) => {
4222
- onSearchChange(event);
4223
- setOpenSearchResult(true);
4224
- }, autoComplete: "off", ref: ref }), jsx(PopoverTitle, {}), showTotalAndLimit && (jsx(Text, { children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${count}, ${enumPickerLabels?.showing ?? formI18n.t('showing')} ${limit}` })), jsxs(Grid, { overflow: 'auto', maxHeight: '20rem', children: [jsx(Flex, { flexFlow: 'column wrap', children: dataList
4225
- .filter((item) => {
4226
- const searchTerm = (searchText || '').toLowerCase();
4227
- if (!searchTerm)
4228
- return true;
4229
- // Check if the original enum value contains the search text
4230
- const enumValueMatch = item
4231
- .toLowerCase()
4232
- .includes(searchTerm);
4233
- // Check if the display value (translation) contains the search text
4234
- const displayValue = !!renderDisplay === true
4235
- ? renderDisplay(item)
4236
- : formI18n.t(item);
4237
- // Convert to string and check if it includes the search term
4238
- const displayValueString = String(displayValue).toLowerCase();
4239
- const displayValueMatch = displayValueString.includes(searchTerm);
4240
- return enumValueMatch || displayValueMatch;
4241
- })
4242
- .map((item) => {
4243
- const selected = isMultiple
4244
- ? watchEnums.some((enumValue) => item === enumValue)
4245
- : watchEnum == item;
4246
- return (jsx(Box, { cursor: 'pointer', onClick: () => {
4247
- if (!isMultiple) {
4248
- setOpenSearchResult(false);
4249
- setValue(colLabel, item);
4250
- return;
4251
- }
4252
- const newSet = new Set([...(watchEnums ?? []), item]);
4253
- setValue(colLabel, [...newSet]);
4254
- }, ...(selected ? { color: 'colorPalette.400/50' } : {}), children: !!renderDisplay === true
4255
- ? renderDisplay(item)
4256
- : formI18n.t(item) }, `${colLabel}-${item}`));
4257
- }) }), isDirty && (jsx(Fragment, { children: dataList.length <= 0 && (jsx(Fragment, { children: enumPickerLabels?.emptySearchResult ??
4258
- formI18n.t('empty_search_result') })) }))] })] }) })] })] }));
4220
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && currentValue.length > 0 && (jsx(Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((enumValue) => {
4221
+ if (!enumValue) {
4222
+ return null;
4223
+ }
4224
+ return (jsx(Tag, { size: "lg", closable: true, onClick: () => {
4225
+ const newValue = currentValue.filter((val) => val !== enumValue);
4226
+ setValue(colLabel, newValue);
4227
+ }, children: !!renderDisplay === true
4228
+ ? renderDisplay(enumValue)
4229
+ : formI18n.t(enumValue) }, enumValue));
4230
+ }) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", children: [jsxs(Combobox.Control, { children: [jsx(Combobox.Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxs(Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
4231
+ setValue(colLabel, '');
4232
+ } })), jsx(Combobox.Trigger, {})] })] }), jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [showTotalAndLimit && (jsx(Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${collection.items.length}` })), collection.items.length === 0 ? (jsx(Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ??
4233
+ formI18n.t('empty_search_result') })) : (jsx(Fragment, { children: collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value))) }))] }) }) })] })] }));
4259
4234
  };
4260
4235
 
4261
4236
  function isEnteringWindow(_ref) {
@@ -4647,9 +4622,7 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
4647
4622
  : files;
4648
4623
  const handleFileClick = (file) => {
4649
4624
  if (multiple) {
4650
- const currentSelection = Array.isArray(selectedFile)
4651
- ? selectedFile
4652
- : [];
4625
+ const currentSelection = Array.isArray(selectedFile) ? selectedFile : [];
4653
4626
  const isAlreadySelected = currentSelection.some((f) => f.id === file.id);
4654
4627
  const newSelection = isAlreadySelected
4655
4628
  ? currentSelection.filter((f) => f.id !== file.id)
@@ -5016,15 +4989,6 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
5016
4989
  }) })] }));
5017
4990
  };
5018
4991
 
5019
- const ToggleTip = React.forwardRef(function ToggleTip(props, ref) {
5020
- const { showArrow, children, portalled = true, content, portalRef, ...rest } = props;
5021
- return (jsxs(Popover.Root, { ...rest, positioning: { ...rest.positioning, gutter: 4 }, children: [jsx(Popover.Trigger, { asChild: true, children: children }), jsx(Portal, { disabled: !portalled, container: portalRef, children: jsx(Popover.Positioner, { children: jsxs(Popover.Content, { width: "auto", px: "2", py: "1", textStyle: "xs", rounded: "sm", ref: ref, children: [showArrow && (jsx(Popover.Arrow, { children: jsx(Popover.ArrowTip, {}) })), content] }) }) })] }));
5022
- });
5023
- const InfoTip = React.forwardRef(function InfoTip(props, ref) {
5024
- const { children, ...rest } = props;
5025
- return (jsx(ToggleTip, { content: children, ...rest, ref: ref, children: jsx(IconButton, { variant: "ghost", "aria-label": "info", size: "2xs", colorPalette: "colorPalette", children: jsx(HiOutlineInformationCircle, {}) }) }));
5026
- });
5027
-
5028
4992
  const getTableData = async ({ serverUrl, in_table, searching = "", where = [], limit = 10, offset = 0, }) => {
5029
4993
  if (serverUrl === undefined || serverUrl.length == 0) {
5030
4994
  throw new Error("The serverUrl is missing");
@@ -5061,10 +5025,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5061
5025
  const isRequired = required?.some((columnId) => columnId === column);
5062
5026
  const { table, column: column_ref, display_column, customQueryFn, } = foreign_key;
5063
5027
  const [searchText, setSearchText] = useState('');
5064
- const [limit, setLimit] = useState(10);
5065
- const [openSearchResult, setOpenSearchResult] = useState();
5066
- const [page, setPage] = useState(0);
5067
- const ref = useRef(null);
5028
+ const [limit] = useState(50); // Increased limit for combobox
5068
5029
  const colLabel = formI18n.colLabel;
5069
5030
  const watchedValue = watch(colLabel);
5070
5031
  const watchId = !isMultiple ? watchedValue : undefined;
@@ -5079,22 +5040,25 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5079
5040
  : [];
5080
5041
  // Use watched values if they exist (including empty string for single select),
5081
5042
  // otherwise fall back to initial values from getValues()
5082
- // This ensures the query can trigger on mount with initial values
5083
- // For single: use watchId if it's not undefined/null, otherwise use initialId
5084
- // For multiple: use watchIds if watchedValue is defined, otherwise use initialIds
5085
5043
  const currentId = watchId !== undefined && watchId !== null ? watchId : initialId;
5086
5044
  const currentIds = watchedValue !== undefined && watchedValue !== null && isMultiple
5087
5045
  ? watchIds
5088
5046
  : initialIds;
5089
- // Query for search results
5047
+ // Current value for combobox (array format)
5048
+ const currentValue = isMultiple
5049
+ ? currentIds.filter((id) => id != null && id !== '')
5050
+ : currentId
5051
+ ? [currentId]
5052
+ : [];
5053
+ // Query for search results (async loading)
5090
5054
  const query = useQuery({
5091
- queryKey: [`idpicker`, { column, searchText, limit, page }],
5055
+ queryKey: [`idpicker`, { column, searchText, limit }],
5092
5056
  queryFn: async () => {
5093
5057
  if (customQueryFn) {
5094
5058
  const { data, idMap } = await customQueryFn({
5095
5059
  searching: searchText ?? '',
5096
5060
  limit: limit,
5097
- offset: page * limit,
5061
+ offset: 0,
5098
5062
  });
5099
5063
  setIdMap((state) => {
5100
5064
  return { ...state, ...idMap };
@@ -5106,7 +5070,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5106
5070
  searching: searchText ?? '',
5107
5071
  in_table: table,
5108
5072
  limit: limit,
5109
- offset: page * limit,
5073
+ offset: 0,
5110
5074
  });
5111
5075
  const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
5112
5076
  return [
@@ -5121,12 +5085,11 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5121
5085
  });
5122
5086
  return data;
5123
5087
  },
5124
- enabled: openSearchResult === true,
5088
+ enabled: true, // Always enabled for combobox
5125
5089
  staleTime: 300000,
5126
5090
  });
5127
5091
  // Query for currently selected items (to display them properly)
5128
- // Use currentId/currentIds in queryKey so it includes initial values and updates when watched values change
5129
- const queryDefault = useQuery({
5092
+ useQuery({
5130
5093
  queryKey: [
5131
5094
  `idpicker-default`,
5132
5095
  {
@@ -5136,11 +5099,9 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5136
5099
  },
5137
5100
  ],
5138
5101
  queryFn: async () => {
5139
- // Use current values (which include initial) for the query
5140
5102
  const queryId = currentId;
5141
5103
  const queryIds = currentIds;
5142
5104
  if (customQueryFn) {
5143
- // For customQueryFn, pass where clause to fetch specific IDs
5144
5105
  const { data, idMap } = await customQueryFn({
5145
5106
  searching: '',
5146
5107
  limit: isMultiple ? queryIds.length : 1,
@@ -5155,10 +5116,9 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5155
5116
  if (!queryId && (!queryIds || queryIds.length === 0)) {
5156
5117
  return { data: [] };
5157
5118
  }
5158
- const searchValue = isMultiple ? queryIds.join(',') : queryId;
5159
5119
  const data = await getTableData({
5160
5120
  serverUrl,
5161
- searching: searchValue,
5121
+ searching: '',
5162
5122
  in_table: table,
5163
5123
  where: [{ id: column_ref, value: isMultiple ? queryIds : queryId }],
5164
5124
  limit: isMultiple ? queryIds.length : 1,
@@ -5181,101 +5141,80 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5181
5141
  ? Array.isArray(currentIds) && currentIds.length > 0
5182
5142
  : !!currentId,
5183
5143
  });
5184
- // Effect to trigger initial data fetch when popover opens
5185
- useEffect(() => {
5186
- if (openSearchResult) {
5187
- // Reset search text when opening the popover
5188
- setSearchText('');
5189
- // Reset page to first page
5190
- setPage(0);
5191
- // Fetch initial data
5192
- query.refetch();
5193
- }
5194
- // eslint-disable-next-line react-hooks/exhaustive-deps
5195
- }, [openSearchResult]);
5196
- const onSearchChange = async (event) => {
5197
- setSearchText(event.target.value);
5198
- setPage(0);
5199
- query.refetch();
5200
- };
5201
- const handleLimitChange = (event) => {
5202
- const newLimit = Number(event.target.value);
5203
- setLimit(newLimit);
5204
- // Reset to first page when changing limit
5205
- setPage(0);
5206
- // Trigger a new search with the updated limit
5207
- query.refetch();
5208
- };
5209
5144
  const { isLoading, isFetching, data, isPending, isError } = query;
5210
5145
  const dataList = data?.data ?? [];
5211
- const count = data?.count ?? 0;
5212
- const getPickedValue = () => {
5213
- if (Object.keys(idMap).length <= 0) {
5214
- return '';
5215
- }
5216
- // Use currentId which includes initial values
5217
- const record = idMap[currentId];
5218
- if (record === undefined) {
5219
- return '';
5146
+ // Transform data for combobox collection
5147
+ const comboboxItems = useMemo(() => {
5148
+ return dataList.map((item) => ({
5149
+ label: !!renderDisplay === true
5150
+ ? String(renderDisplay(item))
5151
+ : String(item[display_column] ?? ''),
5152
+ value: String(item[column_ref]),
5153
+ raw: item,
5154
+ }));
5155
+ }, [dataList, display_column, column_ref, renderDisplay]);
5156
+ // Use filter hook for combobox
5157
+ const { contains } = useFilter({ sensitivity: 'base' });
5158
+ // Create collection for combobox
5159
+ const { collection, filter, set } = useListCollection({
5160
+ initialItems: comboboxItems,
5161
+ itemToString: (item) => item.label,
5162
+ itemToValue: (item) => item.value,
5163
+ filter: contains,
5164
+ });
5165
+ // Handle input value change (search)
5166
+ const handleInputValueChange = (details) => {
5167
+ setSearchText(details.inputValue);
5168
+ // Filter will be applied after data is fetched
5169
+ };
5170
+ // Handle value change
5171
+ const handleValueChange = (details) => {
5172
+ if (isMultiple) {
5173
+ setValue(colLabel, details.value);
5220
5174
  }
5221
- if (!!renderDisplay === true) {
5222
- return renderDisplay(record);
5175
+ else {
5176
+ setValue(colLabel, details.value[0] || '');
5223
5177
  }
5224
- return record[display_column];
5225
5178
  };
5179
+ // Debounce search to avoid too many API calls and update collection after data loads
5180
+ useEffect(() => {
5181
+ const timer = setTimeout(() => {
5182
+ if (searchText !== undefined) {
5183
+ query.refetch();
5184
+ }
5185
+ }, 300);
5186
+ return () => clearTimeout(timer);
5187
+ }, [searchText, query]);
5188
+ // Update collection and filter when data changes
5189
+ useEffect(() => {
5190
+ if (dataList.length > 0) {
5191
+ set(comboboxItems);
5192
+ // Apply filter to the collection
5193
+ if (searchText) {
5194
+ filter(searchText);
5195
+ }
5196
+ }
5197
+ }, [dataList, comboboxItems, set, filter, searchText]);
5226
5198
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5227
- gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && (jsxs(Flex, { flexFlow: 'wrap', gap: 1, children: [watchIds.map((id) => {
5228
- const item = idMap[id];
5229
- if (item === undefined) {
5230
- return (jsx(Text, { children: idPickerLabels?.undefined ?? formI18n.t('undefined') }, id));
5231
- }
5232
- return (jsx(Tag, { closable: true, onClick: () => {
5233
- setValue(colLabel, watchIds.filter((itemId) => itemId !== item[column_ref]));
5234
- }, children: !!renderDisplay === true
5235
- ? renderDisplay(item)
5236
- : item[display_column] }, id));
5237
- }), jsx(Tag, { cursor: 'pointer', onClick: () => {
5238
- setOpenSearchResult(true);
5239
- }, children: idPickerLabels?.addMore ?? formI18n.t('add_more') })] })), !isMultiple && (jsx(Button, { variant: 'outline', onClick: () => {
5240
- setOpenSearchResult(true);
5241
- }, justifyContent: 'start', children: queryDefault.isLoading ? jsx(Spinner, { size: "sm" }) : getPickedValue() })), jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: 'bottom-start', strategy: 'fixed' }, children: [jsx(PopoverTrigger, {}), jsx(PopoverContent, { portalled: false, children: jsxs(PopoverBody, { display: 'grid', gap: 1, children: [jsx(Input, { placeholder: idPickerLabels?.typeToSearch ?? formI18n.t('type_to_search'), onChange: onSearchChange, autoComplete: "off", ref: ref, value: searchText }), jsx(PopoverTitle, {}), openSearchResult && (jsxs(Fragment, { children: [(isFetching || isLoading || isPending) && jsx(Spinner, {}), isError && (jsx(Icon, { color: 'red.400', children: jsx(BiError, {}) })), jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [jsxs(Flex, { alignItems: "center", gap: "2", children: [jsx(InfoTip, { children: `${idPickerLabels?.total ?? formI18n.t('total')} ${count}, ${idPickerLabels?.showing ?? formI18n.t('showing')} ${limit} ${idPickerLabels?.perPage ?? formI18n.t('per_page', { defaultValue: 'per page' })}` }), jsxs(Text, { fontSize: "sm", fontWeight: "bold", children: [count, jsxs(Text, { as: "span", fontSize: "xs", ml: "1", color: "gray.500", children: ["/", ' ', count > 0
5242
- ? `${page * limit + 1}-${Math.min((page + 1) * limit, count)}`
5243
- : '0'] })] })] }), jsx(Box, { children: jsxs("select", { value: limit, onChange: handleLimitChange, style: {
5244
- padding: '4px 8px',
5245
- borderRadius: '4px',
5246
- border: '1px solid #ccc',
5247
- fontSize: '14px',
5248
- }, children: [jsx("option", { value: "5", children: "5" }), jsx("option", { value: "10", children: "10" }), jsx("option", { value: "20", children: "20" }), jsx("option", { value: "30", children: "30" })] }) })] }), jsx(Grid, { overflowY: 'auto', children: dataList.length > 0 ? (jsx(Flex, { flexFlow: 'column wrap', gap: 1, children: dataList.map((item) => {
5249
- const selected = isMultiple
5250
- ? watchIds.some((id) => item[column_ref] === id)
5251
- : watchId === item[column_ref];
5252
- return (jsx(Box, { cursor: 'pointer', onClick: () => {
5253
- if (!isMultiple) {
5254
- setOpenSearchResult(false);
5255
- setValue(colLabel, item[column_ref]);
5256
- return;
5257
- }
5258
- // For multiple selection, don't add if already selected
5259
- if (selected)
5260
- return;
5261
- const newSet = new Set([
5262
- ...(watchIds ?? []),
5263
- item[column_ref],
5264
- ]);
5265
- setValue(colLabel, [...newSet]);
5266
- }, opacity: 0.7, _hover: { opacity: 1 }, ...(selected
5267
- ? {
5268
- color: 'colorPalette.400/50',
5269
- fontWeight: 'bold',
5270
- }
5271
- : {}), children: !!renderDisplay === true
5272
- ? renderDisplay(item)
5273
- : item[display_column] }, item[column_ref]));
5274
- }) })) : (jsx(Text, { children: searchText
5275
- ? idPickerLabels?.emptySearchResult ??
5276
- formI18n.t('empty_search_result')
5277
- : idPickerLabels?.initialResults ??
5278
- formI18n.t('initial_results') })) }), jsx(PaginationRoot, { justifySelf: 'center', count: count, pageSize: limit, defaultPage: 1, page: page + 1, onPageChange: (e) => setPage(e.page - 1), children: jsxs(HStack, { gap: "4", children: [jsx(PaginationPrevTrigger, {}), count > 0 && jsx(PaginationPageText, {}), jsx(PaginationNextTrigger, {})] }) })] }))] }) })] })] }));
5199
+ gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && currentValue.length > 0 && (jsx(Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((id) => {
5200
+ const item = idMap[id];
5201
+ if (item === undefined) {
5202
+ return (jsx(Text, { fontSize: "sm", children: idPickerLabels?.undefined ?? formI18n.t('undefined') }, id));
5203
+ }
5204
+ return (jsx(Tag, { closable: true, onClick: () => {
5205
+ const newValue = currentValue.filter((itemId) => itemId !== id);
5206
+ setValue(colLabel, newValue);
5207
+ }, children: !!renderDisplay === true
5208
+ ? renderDisplay(item)
5209
+ : item[display_column] }, id));
5210
+ }) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", children: [jsxs(Combobox.Control, { children: [jsx(Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxs(Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && (jsx(Spinner, { size: "xs" })), isError && (jsx(Icon, { color: "fg.error", children: jsx(BiError, {}) })), !isMultiple && currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
5211
+ setValue(colLabel, '');
5212
+ } })), jsx(Combobox.Trigger, {})] })] }), jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isFetching || isLoading || isPending ? (jsxs(HStack, { p: 2, justify: "center", children: [jsx(Spinner, { size: "xs" }), jsx(Text, { fontSize: "sm", children: idPickerLabels?.loading ?? formI18n.t('loading') })] })) : isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.loadingFailed ??
5213
+ formI18n.t('loading_failed') })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
5214
+ ? idPickerLabels?.emptySearchResult ??
5215
+ formI18n.t('empty_search_result')
5216
+ : idPickerLabels?.initialResults ??
5217
+ formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value))) })) }) }) })] })] }));
5279
5218
  };
5280
5219
 
5281
5220
  const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsol-oss/react-datatable5",
3
- "version": "12.0.0-beta.84",
3
+ "version": "12.0.0-beta.85",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",