diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c9297421a..01f749662 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: - name: Prettify code uses: creyD/prettier_action@v3.1 with: - prettier_options: --write packages/*/src/** ./*.js examples/**/* + prettier_options: --write packages/*/src/** packages/*/dev/** ./*.js examples/**/* prettier_version: 2.6.2 commit_message: 'Prettified code' env: diff --git a/.prettierrc.json b/.prettierrc.json index a52a3491b..2448074f4 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -6,5 +6,13 @@ "trailingComma": "es5", "bracketSpacing": true, "bracketSameLine": true, - "arrowParens": "avoid" + "arrowParens": "avoid", + "overrides": [ + { + "files": "examples/*/**", + "options": { + "printWidth": 80 + } + } + ] } diff --git a/examples/_template/src/index.scss b/examples/_template/src/index.scss index c76dbdb71..96dc3eca5 100644 --- a/examples/_template/src/index.scss +++ b/examples/_template/src/index.scss @@ -6,7 +6,8 @@ body { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } // __SCSS_POST__ diff --git a/examples/antd/src/index.scss b/examples/antd/src/index.scss index e693fb993..a41767987 100644 --- a/examples/antd/src/index.scss +++ b/examples/antd/src/index.scss @@ -6,7 +6,8 @@ body { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } .queryBuilder { diff --git a/examples/basic-ts/src/App.tsx b/examples/basic-ts/src/App.tsx index b46eb1285..0ec21d9a7 100644 --- a/examples/basic-ts/src/App.tsx +++ b/examples/basic-ts/src/App.tsx @@ -17,7 +17,11 @@ export const App = () => { return (
{formatQuery(query, 'json')}
diff --git a/examples/basic-ts/src/index.scss b/examples/basic-ts/src/index.scss index 8976afdcd..655a922b9 100644 --- a/examples/basic-ts/src/index.scss +++ b/examples/basic-ts/src/index.scss @@ -5,5 +5,6 @@ body { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } diff --git a/examples/basic/src/App.jsx b/examples/basic/src/App.jsx index 8e561fca1..37430e9b9 100644 --- a/examples/basic/src/App.jsx +++ b/examples/basic/src/App.jsx @@ -15,7 +15,11 @@ export const App = () => { return (-setQuery(q)} /> + setQuery(q)} + /> Query
{formatQuery(query, 'json')}
diff --git a/examples/basic/src/index.css b/examples/basic/src/index.css index 695be9711..311f6d39f 100644 --- a/examples/basic/src/index.css +++ b/examples/basic/src/index.css @@ -5,5 +5,6 @@ body { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } diff --git a/examples/bootstrap/src/index.scss b/examples/bootstrap/src/index.scss index cdb432cd3..73426a509 100644 --- a/examples/bootstrap/src/index.scss +++ b/examples/bootstrap/src/index.scss @@ -7,7 +7,8 @@ body { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } .queryBuilder { diff --git a/examples/bulma/src/index.scss b/examples/bulma/src/index.scss index aa35d5d00..d1a4a667c 100644 --- a/examples/bulma/src/index.scss +++ b/examples/bulma/src/index.scss @@ -8,7 +8,8 @@ body { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } .queryBuilder { diff --git a/examples/chakra/src/index.scss b/examples/chakra/src/index.scss index cb4407b25..7ca0d9daa 100644 --- a/examples/chakra/src/index.scss +++ b/examples/chakra/src/index.scss @@ -5,7 +5,8 @@ body { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } .queryBuilder { diff --git a/examples/ci/index.html b/examples/ci/index.html index 8e069ad58..7d9fb7241 100644 --- a/examples/ci/index.html +++ b/examples/ci/index.html @@ -2,7 +2,9 @@ - +React Query Builder CI diff --git a/examples/ci/src/App.tsx b/examples/ci/src/App.tsx index dce2c4157..f6173636a 100644 --- a/examples/ci/src/App.tsx +++ b/examples/ci/src/App.tsx @@ -1,17 +1,32 @@ import { useReducer, useState } from 'react'; -import type { DefaultRuleGroupType, QueryBuilderProps } from 'react-querybuilder'; -import { defaultValidator, formatQuery, QueryBuilder } from 'react-querybuilder'; +import type { + DefaultRuleGroupType, + QueryBuilderProps, +} from 'react-querybuilder'; +import { + defaultValidator, + formatQuery, + QueryBuilder, +} from 'react-querybuilder'; import { fields } from './fields'; import './index.scss'; import { initialQuery, initialQueryIC } from './initialQuery'; -import type { DefaultQBPropsNoDefaultQuery, DefaultQBPropsNoDefaultQueryIC } from './types'; +import type { + DefaultQBPropsNoDefaultQuery, + DefaultQBPropsNoDefaultQueryIC, +} from './types'; import { defaultOptions, optionsOrder, optionsReducer } from './utils'; export const App = () => { const [query, setQuery] = useState(initialQuery); const [queryIC, setQueryIC] = useState(initialQueryIC); const [options, dispatch] = useReducer(optionsReducer, defaultOptions); - const { useValidation, independentCombinators, parseNumbers, ...commonOptions } = options; + const { + useValidation, + independentCombinators, + parseNumbers, + ...commonOptions + } = options; const commonProps: QueryBuilderProps= { fields, ...commonOptions, @@ -46,7 +61,10 @@ export const App = () => { type="checkbox" checked={options[optionName]} onChange={e => - dispatch({ type: 'update', payload: { optionName, value: e.target.checked } }) + dispatch({ + type: 'update', + payload: { optionName, value: e.target.checked }, + }) } /> {optionName} @@ -58,7 +76,12 @@ export const App = () => { {JSON.stringify( - JSON.parse(formatQuery(queryForFormatting, { format: 'json_without_ids', parseNumbers })), + JSON.parse( + formatQuery(queryForFormatting, { + format: 'json_without_ids', + parseNumbers, + }) + ), null, 2 )} @@ -66,7 +89,10 @@ export const App = () => {Parameterized SQL
{JSON.stringify( - formatQuery(queryForFormatting, { format: 'parameterized', parseNumbers }), + formatQuery(queryForFormatting, { + format: 'parameterized', + parseNumbers, + }), null, 2 )} @@ -74,23 +100,37 @@ export const App = () => {Parameterized (Named) SQL
{JSON.stringify( - formatQuery(queryForFormatting, { format: 'parameterized_named', parseNumbers }), + formatQuery(queryForFormatting, { + format: 'parameterized_named', + parseNumbers, + }), null, 2 )}SQL
-{formatQuery(queryForFormatting, { format: 'sql', parseNumbers })}++ {formatQuery(queryForFormatting, { format: 'sql', parseNumbers })} +MongoDB
-{formatQuery(queryForFormatting, { format: 'mongodb', parseNumbers })}++ {formatQuery(queryForFormatting, { format: 'mongodb', parseNumbers })} +CEL
-{formatQuery(queryForFormatting, { format: 'cel', parseNumbers })}++ {formatQuery(queryForFormatting, { format: 'cel', parseNumbers })} +SpEL
-{formatQuery(queryForFormatting, { format: 'spel', parseNumbers })}++ {formatQuery(queryForFormatting, { format: 'spel', parseNumbers })} +JsonLogic
{JSON.stringify( - formatQuery(queryForFormatting, { format: 'jsonlogic', parseNumbers }), + formatQuery(queryForFormatting, { + format: 'jsonlogic', + parseNumbers, + }), null, 2 )} diff --git a/examples/ci/src/utils.ts b/examples/ci/src/utils.ts index 0edb2165f..304f4f48d 100644 --- a/examples/ci/src/utils.ts +++ b/examples/ci/src/utils.ts @@ -34,7 +34,10 @@ export const optionsOrder: CIOption[] = [ 'parseNumbers', ]; -export const optionsReducer = (state: CIOptions, action: CIOptionsAction): CIOptions => { +export const optionsReducer = ( + state: CIOptions, + action: CIOptionsAction +): CIOptions => { const { optionName, value } = action.payload; return { ...state, [optionName]: value }; }; diff --git a/examples/generateExamples.mjs b/examples/generateExamples.mjs index a6897ea39..ced94b64a 100644 --- a/examples/generateExamples.mjs +++ b/examples/generateExamples.mjs @@ -151,6 +151,7 @@ for (const exampleID in configs) { const fileContents = (await readFile(filePath)).toString('utf-8'); const prettified = prettier.format(fileContents, { ...prettierConfig, + printWidth: 80, // narrower since codesandbox code panel is narrow filepath: filePath, plugins: ['prettier-plugin-organize-imports'], }); diff --git a/examples/material/src/index.scss b/examples/material/src/index.scss index 8976afdcd..655a922b9 100644 --- a/examples/material/src/index.scss +++ b/examples/material/src/index.scss @@ -5,5 +5,6 @@ body { } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; } diff --git a/package.json b/package.json index 4bcf48de7..ff16b3f24 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "test:watch": "jest --watch", "type-check": "yarn workspaces run type-check && tsc -p examples", "publish:demo": "yarn build && node gh-pages.publish.js", - "pretty-print": "prettier --write packages/*/src/** ./*.js examples/**/*", + "pretty-print": "prettier --write packages/*/src/** packages/*/dev/** ./*.js examples/**/*", "generate-changelog": "github-changes --owner react-querybuilder --repository react-querybuilder --auth --use-commit-body --only-pulls --date-format=\"(YYYY-MM-DD)\"", "generate-examples": "node ./examples/generateExamples.mjs" }, diff --git a/packages/antd/dev/main.tsx b/packages/antd/dev/main.tsx index 63c1208ea..ead034e51 100644 --- a/packages/antd/dev/main.tsx +++ b/packages/antd/dev/main.tsx @@ -1,8 +1,8 @@ +import 'antd/dist/antd.compact.css'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { App } from 'react-querybuilder/dev'; import { antdControlElements } from '../src'; -import 'antd/dist/antd.compact.css'; import './styles.scss'; createRoot(document.getElementById('app')!).render( diff --git a/packages/antd/dev/styles.scss b/packages/antd/dev/styles.scss index 4ab0a0153..24af87637 100644 --- a/packages/antd/dev/styles.scss +++ b/packages/antd/dev/styles.scss @@ -1,3 +1,3 @@ -body { - margin: 1rem; +.ant-input { + width: auto; } diff --git a/packages/antd/src/AntD.test.tsx b/packages/antd/src/AntD.test.tsx index 7ab17ca59..5d31b4260 100644 --- a/packages/antd/src/AntD.test.tsx +++ b/packages/antd/src/AntD.test.tsx @@ -1,22 +1,20 @@ -import { render, screen, within } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import type { SelectProps } from 'antd'; +import type { OptionProps } from 'antd/lib/select'; import moment from 'moment'; +import type { OptionGroupFC } from 'rc-select/lib/OptGroup'; import { defaultNotToggleProps, defaultValueEditorProps, - defaultValueSelectorProps, findInput, hasOrInheritsClass, testActionElement, testDragHandle, testValueEditor, + testValueSelector, userEventSetup, } from 'react-querybuilder/genericTests'; -import type { - NameLabelPair, - NotToggleProps, - ValueEditorProps, - ValueSelectorProps, -} from 'react-querybuilder/src'; +import type { NotToggleProps, ValueEditorProps } from 'react-querybuilder/src'; import { AntDActionElement, AntDDragHandle, @@ -25,96 +23,39 @@ import { AntDValueSelector, } from '.'; -const antdValueSelectorProps: ValueSelectorProps = { - ...defaultValueSelectorProps, - title: AntDValueSelector.displayName, -}; -const antdValueEditorProps: ValueEditorProps = { - ...defaultValueEditorProps, - type: 'select', - title: AntDValueEditor.displayName, - values: defaultValueSelectorProps.options, -}; - -const testAntDValueSelector = ( - title: string, - Component: React.ComponentType| React.ComponentType , - props: any -) => { - const user = userEventSetup(); - const testValues: NameLabelPair[] = props.values ?? props.options; - const testVal = testValues[1]; - - describe(title, () => { - it('should render the correct number of options', async () => { - render( ); - await user.click(screen.getByRole('combobox')); - const listbox = within(screen.getByRole('listbox')); - expect(listbox.getAllByRole('option')).toHaveLength(2); - }); - - it('should have the options passed into the ', async () => { - render( ); - await user.click(screen.getByRole('combobox')); - const listbox = within(screen.getByRole('listbox')); - expect(listbox.getAllByRole('option')[1]).toHaveTextContent(testVal.name); - }); - - if (('values' in props && props.type === 'multiselect') || 'options' in props) { - it('should have the values passed into the ', () => { - const value = testValues.map(v => v.name).join(','); - const multiselectProps = 'values' in props ? { type: 'multiselect' } : { multiple: true }; - render( ); - expect( - screen.getByTitle(props.title).querySelectorAll('.ant-select-selection-item-content') - ).toHaveLength(testValues.length); - }); - } - - if (('values' in props && props.type !== 'multiselect') || 'options' in props) { - it('should have the value passed into the ', () => { - render( ); - expect(screen.getByTitle(props.title)).toHaveTextContent(testVal.label); - }); - } - - it('should call the onChange method passed in', async () => { - const handleOnChange = jest.fn(); - render( ); - await user.click(screen.getByRole('combobox')); - await user.click(screen.getByText(testVal.label)); - expect(handleOnChange).toHaveBeenCalledWith(testVal.name); - }); - - it('should have the className passed into the ', () => { - render( ); - expect(screen.getByTitle(props.title)).toHaveClass('foo'); - }); - - it('should render optgroups', async () => { - const optGroups = [ - { label: 'Test Option Group', options: 'values' in props ? props.values : props.options }, - ]; - const newProps = - 'values' in props ? { ...props, values: optGroups } : { ...props, options: optGroups }; - render( ); - await user.click(screen.getByRole('combobox')); - const listbox = within(screen.getByRole('listbox')); - expect(listbox.getAllByRole('option').pop()).toHaveTextContent(testVal.name); - }); - - it('should be disabled by the disabled prop', async () => { - const handleOnChange = jest.fn(); - render( ); - expect(screen.getByRole('combobox')).toBeDisabled(); - await user.click(screen.getByRole('combobox')); - expect(() => screen.getByRole('listbox')).toThrow(); - expect(handleOnChange).not.toHaveBeenCalled(); - }); - }); -}; +jest.mock('antd', () => { + // We only mock Select. Everything else can use the real antd components. + const AntD = jest.requireActual('antd'); + + const Select = (props: SelectProps) => ( + + ); + Select.Option = ({ value, children }: OptionProps) => ; + Select.OptGroup = (({ label, children }) => ( + + )) as OptionGroupFC; + + return { ...AntD, Select }; +}); +const valueEditorTitle = AntDValueEditor.displayName; const notToggleTitle = AntDNotToggle.displayName; + describe(notToggleTitle, () => { const user = userEventSetup(); const label = 'Not'; @@ -146,7 +87,6 @@ describe(notToggleTitle, () => { }); }); -const valueEditorTitle = AntDValueEditor.displayName; describe(`${valueEditorTitle} as switch`, () => { const user = userEventSetup(); const props: ValueEditorProps = { @@ -284,16 +224,5 @@ describe(`${valueEditorTitle} date/time pickers`, () => { testActionElement(AntDActionElement); testDragHandle(AntDDragHandle); -testValueEditor(AntDValueEditor, { multiselect: true, select: true, switch: true }); -const valueEditorAsSelectTitle = `${antdValueEditorProps.title} (as ValueSelector)`; -testAntDValueSelector(valueEditorAsSelectTitle, AntDValueEditor, { - ...antdValueEditorProps, - title: valueEditorAsSelectTitle, -}); -const valueEditorAsMultiselectTitle = `${antdValueEditorProps.title} (as ValueSelector multiselect)`; -testAntDValueSelector(valueEditorAsMultiselectTitle, AntDValueEditor, { - ...antdValueEditorProps, - title: valueEditorAsMultiselectTitle, - type: 'multiselect', -}); -testAntDValueSelector(antdValueSelectorProps.title!, AntDValueSelector, antdValueSelectorProps); +testValueEditor(AntDValueEditor, { switch: true }); +testValueSelector(AntDValueSelector); diff --git a/packages/antd/src/AntDValueEditor.tsx b/packages/antd/src/AntDValueEditor.tsx index 20471543f..9b3c37da0 100644 --- a/packages/antd/src/AntDValueEditor.tsx +++ b/packages/antd/src/AntDValueEditor.tsx @@ -1,7 +1,13 @@ import { Checkbox, DatePicker, Input, Radio, Switch, TimePicker } from 'antd'; import moment from 'moment'; -import { useEffect } from 'react'; -import type { ValueEditorProps } from 'react-querybuilder'; +import { + joinWith, + splitBy, + standardClassnames, + toArray, + useValueEditor, + type ValueEditorProps, +} from 'react-querybuilder'; import { AntDValueSelector } from './AntDValueSelector'; export const AntDValueEditor = ({ @@ -13,21 +19,14 @@ export const AntDValueEditor = ({ className, type, inputType, - values, + values = [], + listsAsArrays, valueSource: _vs, disabled, + testID, ...props }: ValueEditorProps) => { - useEffect(() => { - if ( - inputType === 'number' && - !['between', 'notBetween', 'in', 'notIn'].includes(operator) && - typeof value === 'string' && - value.includes(',') - ) { - handleOnChange(''); - } - }, [inputType, operator, value, handleOnChange]); + useValueEditor({ handleOnChange, inputType, operator, value }); if (operator === 'null' || operator === 'notNull') { return null; @@ -42,6 +41,40 @@ export const AntDValueEditor = ({ ? 'text' : inputType || 'text'; + if ((operator === 'between' || operator === 'notBetween') && type === 'select') { + const valArray = toArray(value); + const selector1handler = (v: string) => { + const val = [v, valArray[1] ?? values[0]?.name, ...valArray.slice(2)]; + handleOnChange(listsAsArrays ? val : joinWith(val, ',')); + }; + const selector2handler = (v: string) => { + const val = [valArray[0], v, ...valArray.slice(2)]; + handleOnChange(listsAsArrays ? val : joinWith(val, ',')); + }; + return ( + + + + + ); + } + switch (type) { case 'select': case 'multiselect': @@ -50,11 +83,12 @@ export const AntDValueEditor = ({ {...props} className={className} handleOnChange={handleOnChange} - options={values!} + options={values} value={value} title={title} disabled={disabled} multiple={type === 'multiselect'} + listsAsArrays={listsAsArrays} /> ); @@ -96,7 +130,7 @@ export const AntDValueEditor = ({ case 'radio': return ( - {values!.map(v => ( + {values.map(v => ( moment(v)) as [moment.Moment, moment.Moment]) + ? (splitBy(value, ',').map(v => moment(v)) as [moment.Moment, moment.Moment]) : undefined } showTime={inputTypeCoerced === 'datetime-local'} diff --git a/packages/antd/src/AntDValueSelector.tsx b/packages/antd/src/AntDValueSelector.tsx index 31044e2d1..9c17f6b79 100644 --- a/packages/antd/src/AntDValueSelector.tsx +++ b/packages/antd/src/AntDValueSelector.tsx @@ -1,6 +1,6 @@ import { Select } from 'antd'; import { useMemo, type ComponentPropsWithoutRef } from 'react'; -import type { VersatileSelectorProps } from 'react-querybuilder'; +import { joinWith, splitBy, type VersatileSelectorProps } from 'react-querybuilder'; import { toOptions } from './utils'; type AntDValueSelectorProps = VersatileSelectorProps & @@ -14,6 +14,7 @@ export const AntDValueSelector = ({ title, disabled, multiple, + listsAsArrays, // Props that should not be in extraProps testID: _testID, rules: _rules, @@ -26,18 +27,25 @@ export const AntDValueSelector = ({ fieldData: _fieldData, ...extraProps }: AntDValueSelectorProps) => { - const onChange = useMemo(() => { - if (multiple) { - return (v: string | string[]) => - handleOnChange(Array.isArray(v) ? v.join(',') : /* istanbul ignore next */ v); - } - return (v: string) => handleOnChange(v); - }, [handleOnChange, multiple]); + const onChange = useMemo( + () => + multiple + ? (v: string | string[]) => + handleOnChange( + Array.isArray(v) + ? listsAsArrays + ? v + : joinWith(v, ',') + : /* istanbul ignore next */ v + ) + : (v: string) => handleOnChange(v), + [handleOnChange, listsAsArrays, multiple] + ); const val = multiple ? Array.isArray(value) ? /* istanbul ignore next */ value - : value?.split(',') + : splitBy(value, ',') : value; const modeObj = multiple ? { mode: 'multiple' as const } : {}; diff --git a/packages/bootstrap/dev/styles.scss b/packages/bootstrap/dev/styles.scss index 3b7937fcc..0ee5b1afa 100644 --- a/packages/bootstrap/dev/styles.scss +++ b/packages/bootstrap/dev/styles.scss @@ -2,10 +2,6 @@ $code-color: #333333; @import 'bootstrap/scss/bootstrap.scss'; -body { - margin: 1rem; -} - #app > div:first-child > label > input { margin: 3px; } diff --git a/packages/bootstrap/src/BootstrapValueEditor.tsx b/packages/bootstrap/src/BootstrapValueEditor.tsx index 8390e1ac1..bfe5cc4a1 100644 --- a/packages/bootstrap/src/BootstrapValueEditor.tsx +++ b/packages/bootstrap/src/BootstrapValueEditor.tsx @@ -1,5 +1,11 @@ -import { useEffect } from 'react'; -import { ValueSelector, type ValueEditorProps } from 'react-querybuilder'; +import { + joinWith, + standardClassnames as sc, + toArray, + useValueEditor, + ValueSelector, + type ValueEditorProps, +} from 'react-querybuilder'; export const BootstrapValueEditor = ({ fieldData, @@ -10,20 +16,13 @@ export const BootstrapValueEditor = ({ className, type, inputType, - values, + values = [], + listsAsArrays, disabled, + testID, ...props }: ValueEditorProps) => { - useEffect(() => { - if ( - inputType === 'number' && - !['between', 'notBetween', 'in', 'notIn'].includes(operator) && - typeof value === 'string' && - value.includes(',') - ) { - handleOnChange(''); - } - }, [inputType, operator, value, handleOnChange]); + useValueEditor({ handleOnChange, inputType, operator, value }); if (operator === 'null' || operator === 'notNull') { return null; @@ -34,6 +33,40 @@ export const BootstrapValueEditor = ({ ? 'text' : inputType || 'text'; + if ((operator === 'between' || operator === 'notBetween') && type === 'select') { + const valArray = toArray(value); + const selector1handler = (v: string) => { + const val = [v, valArray[1] ?? values[0]?.name, ...valArray.slice(2)]; + handleOnChange(listsAsArrays ? val : joinWith(val, ',')); + }; + const selector2handler = (v: string) => { + const val = [valArray[0], v, ...valArray.slice(2)]; + handleOnChange(listsAsArrays ? val : joinWith(val, ',')); + }; + return ( + + + + + ); + } + switch (type) { case 'select': case 'multiselect': @@ -46,7 +79,8 @@ export const BootstrapValueEditor = ({ value={value} disabled={disabled} multiple={type === 'multiselect'} - options={values!} + listsAsArrays={listsAsArrays} + options={values} /> ); @@ -91,7 +125,7 @@ export const BootstrapValueEditor = ({ case 'radio': return ( - {values!.map(v => ( + {values.map(v => ( { - useEffect(() => { - if ( - inputType === 'number' && - !['between', 'notBetween', 'in', 'notIn'].includes(operator) && - typeof value === 'string' && - value.includes(',') - ) { - handleOnChange(''); - } - }, [inputType, operator, value, handleOnChange]); + useValueEditor({ handleOnChange, inputType, operator, value }); if (operator === 'null' || operator === 'notNull') { return null; @@ -35,6 +33,40 @@ export const BulmaValueEditor = ({ ? 'text' : inputType || 'text'; + if ((operator === 'between' || operator === 'notBetween') && type === 'select') { + const valArray = toArray(value); + const selector1handler = (v: string) => { + const val = [v, valArray[1] ?? values[0]?.name, ...valArray.slice(2)]; + handleOnChange(listsAsArrays ? val : joinWith(val, ',')); + }; + const selector2handler = (v: string) => { + const val = [valArray[0], v, ...valArray.slice(2)]; + handleOnChange(listsAsArrays ? val : joinWith(val, ',')); + }; + return ( + ++ + + ); + } + switch (type) { case 'select': case 'multiselect': @@ -44,10 +76,11 @@ export const BulmaValueEditor = ({ title={title} className={className} handleOnChange={handleOnChange} - options={values!} + options={values} value={value} disabled={disabled} multiple={type === 'multiselect'} + listsAsArrays={listsAsArrays} /> ); @@ -81,7 +114,7 @@ export const BulmaValueEditor = ({ case 'radio': return ( - {values!.map(v => ( + {values.map(v => (