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

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
@@ -853,6 +853,7 @@ interface FormRootProps<TData extends FieldValues> {
853
853
  idPickerLabels?: IdPickerLabels;
854
854
  enumPickerLabels?: EnumPickerLabels;
855
855
  filePickerLabels?: FilePickerLabels;
856
+ comboboxInDialog?: boolean;
856
857
  }
857
858
  interface CustomJSONSchema7Definition extends JSONSchema7 {
858
859
  variant: string;
@@ -869,7 +870,7 @@ declare const idPickerSanityCheck: (column: string, foreign_key?: {
869
870
  column?: string | undefined;
870
871
  display_column?: string | undefined;
871
872
  } | undefined) => void;
872
- declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
873
+ declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, comboboxInDialog, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
873
874
 
874
875
  interface DefaultFormProps<TData extends FieldValues> {
875
876
  formConfig: Omit<FormRootProps<TData>, "children">;
package/dist/index.js CHANGED
@@ -3627,7 +3627,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3627
3627
  showSubmitButton: true,
3628
3628
  showResetButton: true,
3629
3629
  showTitle: true,
3630
- }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }) => {
3630
+ }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, comboboxInDialog = false, }) => {
3631
3631
  const [isSuccess, setIsSuccess] = React.useState(false);
3632
3632
  const [isError, setIsError] = React.useState(false);
3633
3633
  const [isSubmiting, setIsSubmiting] = React.useState(false);
@@ -3718,6 +3718,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3718
3718
  enumPickerLabels,
3719
3719
  filePickerLabels,
3720
3720
  ajvResolver: ajvResolver(schema),
3721
+ comboboxInDialog,
3721
3722
  }, children: jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: children }) }));
3722
3723
  };
3723
3724
 
@@ -4178,7 +4179,7 @@ const DateRangePicker = ({ column, schema, prefix, }) => {
4178
4179
 
4179
4180
  const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLimit = false, }) => {
4180
4181
  const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
4181
- const { enumPickerLabels } = useSchemaContext();
4182
+ const { enumPickerLabels, comboboxInDialog } = useSchemaContext();
4182
4183
  const formI18n = useFormI18n(column, prefix);
4183
4184
  const { required, variant } = schema;
4184
4185
  const isRequired = required?.some((columnId) => columnId === column);
@@ -4247,10 +4248,13 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4247
4248
  }, children: !!renderDisplay === true
4248
4249
  ? renderDisplay(enumValue)
4249
4250
  : 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
+ }) })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: comboboxInDialog
4252
+ ? { strategy: 'fixed', hideWhenDetached: true }
4253
+ : undefined, 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
4254
  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))) }))] }) }) })] })] }));
4255
+ } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), comboboxInDialog ? (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 ??
4256
+ formI18n.t('empty_search_result') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (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 ??
4257
+ formI18n.t('empty_search_result') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
4254
4258
  };
4255
4259
 
4256
4260
  function isEnteringWindow(_ref) {
@@ -5039,12 +5043,13 @@ const getTableData = async ({ serverUrl, in_table, searching = "", where = [], l
5039
5043
 
5040
5044
  const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5041
5045
  const { watch, getValues, formState: { errors }, setValue, } = reactHookForm.useFormContext();
5042
- const { serverUrl, idMap, setIdMap, schema: parentSchema, idPickerLabels, } = useSchemaContext();
5046
+ const { serverUrl, idMap, setIdMap, schema: parentSchema, idPickerLabels, comboboxInDialog, } = useSchemaContext();
5043
5047
  const formI18n = useFormI18n(column, prefix);
5044
5048
  const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, foreign_key, } = schema;
5045
5049
  const isRequired = required?.some((columnId) => columnId === column);
5046
5050
  const { table, column: column_ref, display_column, customQueryFn, } = foreign_key;
5047
5051
  const [searchText, setSearchText] = React.useState('');
5052
+ const [debouncedSearchText, setDebouncedSearchText] = React.useState('');
5048
5053
  const [limit] = React.useState(50); // Increased limit for combobox
5049
5054
  const colLabel = formI18n.colLabel;
5050
5055
  const watchedValue = watch(colLabel);
@@ -5070,13 +5075,20 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5070
5075
  : currentId
5071
5076
  ? [currentId]
5072
5077
  : [];
5078
+ // Debounce search text to avoid too many API calls
5079
+ React.useEffect(() => {
5080
+ const timer = setTimeout(() => {
5081
+ setDebouncedSearchText(searchText);
5082
+ }, 300);
5083
+ return () => clearTimeout(timer);
5084
+ }, [searchText]);
5073
5085
  // Query for search results (async loading)
5074
5086
  const query = reactQuery.useQuery({
5075
- queryKey: [`idpicker`, { column, searchText, limit }],
5087
+ queryKey: [`idpicker`, { column, searchText: debouncedSearchText, limit }],
5076
5088
  queryFn: async () => {
5077
5089
  if (customQueryFn) {
5078
5090
  const { data, idMap } = await customQueryFn({
5079
- searching: searchText ?? '',
5091
+ searching: debouncedSearchText ?? '',
5080
5092
  limit: limit,
5081
5093
  offset: 0,
5082
5094
  });
@@ -5087,7 +5099,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5087
5099
  }
5088
5100
  const data = await getTableData({
5089
5101
  serverUrl,
5090
- searching: searchText ?? '',
5102
+ searching: debouncedSearchText ?? '',
5091
5103
  in_table: table,
5092
5104
  limit: limit,
5093
5105
  offset: 0,
@@ -5109,6 +5121,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5109
5121
  staleTime: 300000,
5110
5122
  });
5111
5123
  // Query for currently selected items (to display them properly)
5124
+ // This query is needed for side effects (populating idMap) even though the result isn't directly used
5112
5125
  reactQuery.useQuery({
5113
5126
  queryKey: [
5114
5127
  `idpicker-default`,
@@ -5163,6 +5176,8 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5163
5176
  });
5164
5177
  const { isLoading, isFetching, data, isPending, isError } = query;
5165
5178
  const dataList = data?.data ?? [];
5179
+ // Check if we're currently searching (user typed but debounce hasn't fired yet)
5180
+ const isSearching = searchText !== debouncedSearchText;
5166
5181
  // Transform data for combobox collection
5167
5182
  const comboboxItems = React.useMemo(() => {
5168
5183
  return dataList.map((item) => ({
@@ -5196,25 +5211,19 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5196
5211
  setValue(colLabel, details.value[0] || '');
5197
5212
  }
5198
5213
  };
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
5214
  // Update collection and filter when data changes
5209
5215
  React.useEffect(() => {
5210
- if (dataList.length > 0) {
5216
+ if (dataList.length > 0 && comboboxItems.length > 0) {
5211
5217
  set(comboboxItems);
5212
- // Apply filter to the collection
5218
+ // Apply filter to the collection using the immediate searchText for UI responsiveness
5213
5219
  if (searchText) {
5214
5220
  filter(searchText);
5215
5221
  }
5216
5222
  }
5217
- }, [dataList, comboboxItems, set, filter, searchText]);
5223
+ // Only depend on dataList and searchText, not comboboxItems (which is derived from dataList)
5224
+ // set and filter are stable functions from useListCollection
5225
+ // eslint-disable-next-line react-hooks/exhaustive-deps
5226
+ }, [dataList, searchText]);
5218
5227
  return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5219
5228
  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
5229
  const item = idMap[id];
@@ -5227,14 +5236,23 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5227
5236
  }, children: !!renderDisplay === true
5228
5237
  ? renderDisplay(item)
5229
5238
  : 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: () => {
5239
+ }) })), jsxRuntime.jsxs(react.Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: comboboxInDialog
5240
+ ? { strategy: 'fixed', hideWhenDetached: true }
5241
+ : undefined, 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
5242
  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
5243
+ } })), jsxRuntime.jsx(react.Combobox.Trigger, {})] })] }), comboboxInDialog ? (jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
5244
+ // Show skeleton items to prevent UI shift
5245
+ jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
5246
+ ? idPickerLabels?.emptySearchResult ??
5247
+ formI18n.t('empty_search_result')
5248
+ : idPickerLabels?.initialResults ??
5249
+ formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsxRuntime.jsx(react.Portal, { children: jsxRuntime.jsx(react.Combobox.Positioner, { children: jsxRuntime.jsx(react.Combobox.Content, { children: isError ? (jsxRuntime.jsx(react.Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
5250
+ // Show skeleton items to prevent UI shift
5251
+ jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsxRuntime.jsx(react.Flex, { p: 2, align: "center", gap: 2, children: jsxRuntime.jsx(react.Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsxRuntime.jsx(react.Combobox.Empty, { children: searchText
5234
5252
  ? idPickerLabels?.emptySearchResult ??
5235
5253
  formI18n.t('empty_search_result')
5236
5254
  : 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))) })) }) }) })] })] }));
5255
+ formI18n.t('initial_results') })) : (jsxRuntime.jsx(jsxRuntime.Fragment, { children: collection.items.map((item, index) => (jsxRuntime.jsxs(react.Combobox.Item, { item: item, children: [jsxRuntime.jsx(react.Combobox.ItemText, { children: item.label }), jsxRuntime.jsx(react.Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
5238
5256
  };
5239
5257
 
5240
5258
  const NumberInputRoot = React__namespace.forwardRef(function NumberInput(props, ref) {
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, 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';
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, Skeleton, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading } from '@chakra-ui/react';
3
3
  import { AiOutlineColumnWidth } from 'react-icons/ai';
4
4
  import * as React from 'react';
5
5
  import React__default, { createContext, useContext, useState, useEffect, useRef, useMemo, forwardRef } from 'react';
@@ -3607,7 +3607,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3607
3607
  showSubmitButton: true,
3608
3608
  showResetButton: true,
3609
3609
  showTitle: true,
3610
- }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }) => {
3610
+ }, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, comboboxInDialog = false, }) => {
3611
3611
  const [isSuccess, setIsSuccess] = useState(false);
3612
3612
  const [isError, setIsError] = useState(false);
3613
3613
  const [isSubmiting, setIsSubmiting] = useState(false);
@@ -3698,6 +3698,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
3698
3698
  enumPickerLabels,
3699
3699
  filePickerLabels,
3700
3700
  ajvResolver: ajvResolver(schema),
3701
+ comboboxInDialog,
3701
3702
  }, children: jsx(FormProvider, { ...form, children: children }) }));
3702
3703
  };
3703
3704
 
@@ -4158,7 +4159,7 @@ const DateRangePicker = ({ column, schema, prefix, }) => {
4158
4159
 
4159
4160
  const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLimit = false, }) => {
4160
4161
  const { watch, formState: { errors }, setValue, } = useFormContext();
4161
- const { enumPickerLabels } = useSchemaContext();
4162
+ const { enumPickerLabels, comboboxInDialog } = useSchemaContext();
4162
4163
  const formI18n = useFormI18n(column, prefix);
4163
4164
  const { required, variant } = schema;
4164
4165
  const isRequired = required?.some((columnId) => columnId === column);
@@ -4227,10 +4228,13 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
4227
4228
  }, children: !!renderDisplay === true
4228
4229
  ? renderDisplay(enumValue)
4229
4230
  : 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
+ }) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: comboboxInDialog
4232
+ ? { strategy: 'fixed', hideWhenDetached: true }
4233
+ : undefined, 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
4234
  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))) }))] }) }) })] })] }));
4235
+ } })), jsx(Combobox.Trigger, {})] })] }), comboboxInDialog ? (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 ??
4236
+ formI18n.t('empty_search_result') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) })) : (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 ??
4237
+ formI18n.t('empty_search_result') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) }))] }) }) }))] })] }));
4234
4238
  };
4235
4239
 
4236
4240
  function isEnteringWindow(_ref) {
@@ -5019,12 +5023,13 @@ const getTableData = async ({ serverUrl, in_table, searching = "", where = [], l
5019
5023
 
5020
5024
  const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5021
5025
  const { watch, getValues, formState: { errors }, setValue, } = useFormContext();
5022
- const { serverUrl, idMap, setIdMap, schema: parentSchema, idPickerLabels, } = useSchemaContext();
5026
+ const { serverUrl, idMap, setIdMap, schema: parentSchema, idPickerLabels, comboboxInDialog, } = useSchemaContext();
5023
5027
  const formI18n = useFormI18n(column, prefix);
5024
5028
  const { required, gridColumn = 'span 12', gridRow = 'span 1', renderDisplay, foreign_key, } = schema;
5025
5029
  const isRequired = required?.some((columnId) => columnId === column);
5026
5030
  const { table, column: column_ref, display_column, customQueryFn, } = foreign_key;
5027
5031
  const [searchText, setSearchText] = useState('');
5032
+ const [debouncedSearchText, setDebouncedSearchText] = useState('');
5028
5033
  const [limit] = useState(50); // Increased limit for combobox
5029
5034
  const colLabel = formI18n.colLabel;
5030
5035
  const watchedValue = watch(colLabel);
@@ -5050,13 +5055,20 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5050
5055
  : currentId
5051
5056
  ? [currentId]
5052
5057
  : [];
5058
+ // Debounce search text to avoid too many API calls
5059
+ useEffect(() => {
5060
+ const timer = setTimeout(() => {
5061
+ setDebouncedSearchText(searchText);
5062
+ }, 300);
5063
+ return () => clearTimeout(timer);
5064
+ }, [searchText]);
5053
5065
  // Query for search results (async loading)
5054
5066
  const query = useQuery({
5055
- queryKey: [`idpicker`, { column, searchText, limit }],
5067
+ queryKey: [`idpicker`, { column, searchText: debouncedSearchText, limit }],
5056
5068
  queryFn: async () => {
5057
5069
  if (customQueryFn) {
5058
5070
  const { data, idMap } = await customQueryFn({
5059
- searching: searchText ?? '',
5071
+ searching: debouncedSearchText ?? '',
5060
5072
  limit: limit,
5061
5073
  offset: 0,
5062
5074
  });
@@ -5067,7 +5079,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5067
5079
  }
5068
5080
  const data = await getTableData({
5069
5081
  serverUrl,
5070
- searching: searchText ?? '',
5082
+ searching: debouncedSearchText ?? '',
5071
5083
  in_table: table,
5072
5084
  limit: limit,
5073
5085
  offset: 0,
@@ -5089,6 +5101,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5089
5101
  staleTime: 300000,
5090
5102
  });
5091
5103
  // Query for currently selected items (to display them properly)
5104
+ // This query is needed for side effects (populating idMap) even though the result isn't directly used
5092
5105
  useQuery({
5093
5106
  queryKey: [
5094
5107
  `idpicker-default`,
@@ -5143,6 +5156,8 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5143
5156
  });
5144
5157
  const { isLoading, isFetching, data, isPending, isError } = query;
5145
5158
  const dataList = data?.data ?? [];
5159
+ // Check if we're currently searching (user typed but debounce hasn't fired yet)
5160
+ const isSearching = searchText !== debouncedSearchText;
5146
5161
  // Transform data for combobox collection
5147
5162
  const comboboxItems = useMemo(() => {
5148
5163
  return dataList.map((item) => ({
@@ -5176,25 +5191,19 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5176
5191
  setValue(colLabel, details.value[0] || '');
5177
5192
  }
5178
5193
  };
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
5194
  // Update collection and filter when data changes
5189
5195
  useEffect(() => {
5190
- if (dataList.length > 0) {
5196
+ if (dataList.length > 0 && comboboxItems.length > 0) {
5191
5197
  set(comboboxItems);
5192
- // Apply filter to the collection
5198
+ // Apply filter to the collection using the immediate searchText for UI responsiveness
5193
5199
  if (searchText) {
5194
5200
  filter(searchText);
5195
5201
  }
5196
5202
  }
5197
- }, [dataList, comboboxItems, set, filter, searchText]);
5203
+ // Only depend on dataList and searchText, not comboboxItems (which is derived from dataList)
5204
+ // set and filter are stable functions from useListCollection
5205
+ // eslint-disable-next-line react-hooks/exhaustive-deps
5206
+ }, [dataList, searchText]);
5198
5207
  return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
5199
5208
  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
5209
  const item = idMap[id];
@@ -5207,14 +5216,23 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
5207
5216
  }, children: !!renderDisplay === true
5208
5217
  ? renderDisplay(item)
5209
5218
  : 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: () => {
5219
+ }) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", positioning: comboboxInDialog
5220
+ ? { strategy: 'fixed', hideWhenDetached: true }
5221
+ : undefined, 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
5222
  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
5223
+ } })), jsx(Combobox.Trigger, {})] })] }), comboboxInDialog ? (jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
5224
+ // Show skeleton items to prevent UI shift
5225
+ jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
5226
+ ? idPickerLabels?.emptySearchResult ??
5227
+ formI18n.t('empty_search_result')
5228
+ : idPickerLabels?.initialResults ??
5229
+ formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) })) : (jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: formI18n.t('loading_failed') })) : isFetching || isLoading || isPending || isSearching ? (
5230
+ // Show skeleton items to prevent UI shift
5231
+ jsx(Fragment, { children: Array.from({ length: 5 }).map((_, index) => (jsx(Flex, { p: 2, align: "center", gap: 2, children: jsx(Skeleton, { height: "20px", flex: "1" }) }, `skeleton-${index}`))) })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
5214
5232
  ? idPickerLabels?.emptySearchResult ??
5215
5233
  formI18n.t('empty_search_result')
5216
5234
  : 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))) })) }) }) })] })] }));
5235
+ formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item, index) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value ?? `item-${index}`))) })) }) }) }))] })] }));
5218
5236
  };
5219
5237
 
5220
5238
  const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
@@ -45,5 +45,6 @@ export interface SchemaFormContext<TData extends FieldValues> {
45
45
  enumPickerLabels?: EnumPickerLabels;
46
46
  filePickerLabels?: FilePickerLabels;
47
47
  ajvResolver: Resolver<FieldValues>;
48
+ comboboxInDialog?: boolean;
48
49
  }
49
50
  export declare const SchemaFormContext: import("react").Context<SchemaFormContext<unknown>>;
@@ -33,6 +33,7 @@ export interface FormRootProps<TData extends FieldValues> {
33
33
  idPickerLabels?: IdPickerLabels;
34
34
  enumPickerLabels?: EnumPickerLabels;
35
35
  filePickerLabels?: FilePickerLabels;
36
+ comboboxInDialog?: boolean;
36
37
  }
37
38
  export interface CustomJSONSchema7Definition extends JSONSchema7 {
38
39
  variant: string;
@@ -49,4 +50,4 @@ export declare const idPickerSanityCheck: (column: string, foreign_key?: {
49
50
  column?: string | undefined;
50
51
  display_column?: string | undefined;
51
52
  } | undefined) => void;
52
- export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
53
+ export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, comboboxInDialog, }: FormRootProps<TData>) => 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": "12.0.0-beta.85",
3
+ "version": "12.0.0-beta.87",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -62,10 +62,10 @@
62
62
  "@rollup/plugin-typescript": "^11.1.6",
63
63
  "@storybook/addon-essentials": "^8.4.7",
64
64
  "@storybook/addon-interactions": "^8.4.7",
65
- "@storybook/addon-onboarding": "^8.4.7",
65
+ "@storybook/addon-onboarding": "^10.0.7",
66
66
  "@storybook/blocks": "^8.4.7",
67
- "@storybook/react": "^8.4.7",
68
- "@storybook/react-vite": "^8.4.7",
67
+ "@storybook/react": "^10.0.7",
68
+ "@storybook/react-vite": "^10.0.7",
69
69
  "@storybook/test": "^8.4.7",
70
70
  "@types/ajv-errors": "^2.0.0",
71
71
  "@types/json-schema": "^7.0.15",
@@ -81,12 +81,12 @@
81
81
  "eslint": "^8.57.0",
82
82
  "eslint-plugin-react-hooks": "^4.6.0",
83
83
  "eslint-plugin-react-refresh": "^0.4.6",
84
- "eslint-plugin-storybook": "^0.8.0",
84
+ "eslint-plugin-storybook": "^10.0.7",
85
85
  "husky": "^9.1.7",
86
86
  "lint-staged": "^16.2.5",
87
87
  "prettier": "3.2.5",
88
88
  "rollup-plugin-dts": "^6.1.0",
89
- "storybook": "^8.4.7",
89
+ "storybook": "^10.0.7",
90
90
  "typescript": "^5.2.2",
91
91
  "vite": "^5.2.0"
92
92
  },