diff --git a/CHANGELOG.md b/CHANGELOG.md
index a8c99a851..9c00dd863 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,6 @@
# Changelog
+- 4.4.2
+ - Added support of autocomplete for multiselect widget in MUI (PR #475)
- 4.4.1
- feat: possibility to add custom operators for groups (PR #462)
- 4.4.0
diff --git a/CONFIG.adoc b/CONFIG.adoc
index 56aeb9258..beee9feb7 100644
--- a/CONFIG.adoc
+++ b/CONFIG.adoc
@@ -335,11 +335,14 @@ Render settings:
|customFieldSelectProps |`{}` |You can pass props to `Select` field widget. Example: `{showSearch: true}`
|groupActionsPosition |`topRight` |You can change the position of the group actions to the following: +
`topLeft, topCenter, topRight, bottomLeft, bottomCenter, bottomRight`
-|renderBeforeWidget| |
-|renderAfterWidget| |
-|renderBeforeActions| |
-|renderAfterActions| |
-|defaultSliderWidth|"200px" |Width for slider
+|renderBeforeWidget | |
+|renderAfterWidget | |
+|renderBeforeActions | |
+|renderAfterActions | |
+|defaultSliderWidth |`200px` |Width for slider
+|defaultSelectWidth |`200px` |Width for select
+|defaultSearchWidth |`100px` |Width for search in autocomplete
+|defaultMaxRows |5 | Max rows for textarea
|===
Other settings:
diff --git a/examples/demo/config.tsx b/examples/demo/config.tsx
index a0571cdec..029d119e4 100644
--- a/examples/demo/config.tsx
+++ b/examples/demo/config.tsx
@@ -131,6 +131,19 @@ export default (skin: string) => {
showSearch: true
}
},
+ select: {
+ ...InitialConfig.widgets.select,
+ },
+ multiselect: {
+ ...InitialConfig.widgets.multiselect,
+ customProps: {
+ //showCheckboxes: false,
+ width: "200px",
+ input: {
+ width: "100px"
+ }
+ }
+ },
treeselect: {
...InitialConfig.widgets.treeselect,
customProps: {
@@ -202,6 +215,9 @@ export default (skin: string) => {
...localeSettings,
defaultSliderWidth: "200px",
+ defaultSelectWidth: "200px",
+ defaultSearchWidth: "100px",
+ defaultMaxRows: 5,
valueSourcesInfo: {
value: {
@@ -454,6 +470,7 @@ export default (skin: string) => {
label: "Colors",
type: "multiselect",
fieldSettings: {
+ showSearch: true,
listValues: {
yellow: "Yellow",
green: "Green",
diff --git a/modules/components/widgets/antd/value/MultiSelect.jsx b/modules/components/widgets/antd/value/MultiSelect.jsx
index 69215ebc0..49851e8a1 100644
--- a/modules/components/widgets/antd/value/MultiSelect.jsx
+++ b/modules/components/widgets/antd/value/MultiSelect.jsx
@@ -4,6 +4,7 @@ import { Select } from "antd";
import {calcTextWidth, SELECT_WIDTH_OFFSET_RIGHT} from "../../../../utils/domUtils";
import {mapListValues} from "../../../../utils/stuff";
import {useOnPropsChanged} from "../../../../utils/reactUtils";
+import omit from "lodash/omit";
const Option = Select.Option;
export default class MultiSelectWidget extends PureComponent {
@@ -59,6 +60,7 @@ export default class MultiSelectWidget extends PureComponent {
const aValue = value && value.length ? value : undefined;
const width = aValue ? null : placeholderWidth + SELECT_WIDTH_OFFSET_RIGHT;
const dropdownWidth = this.optionsMaxWidth + SELECT_WIDTH_OFFSET_RIGHT;
+ const customSelectProps = omit(customProps, ["showCheckboxes"]);
return (
);
diff --git a/modules/components/widgets/antd/value/Select.jsx b/modules/components/widgets/antd/value/Select.jsx
index 600b828dd..882b27a13 100644
--- a/modules/components/widgets/antd/value/Select.jsx
+++ b/modules/components/widgets/antd/value/Select.jsx
@@ -4,6 +4,7 @@ import {calcTextWidth, SELECT_WIDTH_OFFSET_RIGHT} from "../../../../utils/domUti
import {mapListValues} from "../../../../utils/stuff";
import {useOnPropsChanged} from "../../../../utils/reactUtils";
import { Select } from "antd";
+import omit from "lodash/omit";
const Option = Select.Option;
export default class SelectWidget extends PureComponent {
@@ -55,6 +56,7 @@ export default class SelectWidget extends PureComponent {
const dropdownWidth = this.optionsMaxWidth + SELECT_WIDTH_OFFSET_RIGHT;
const width = value ? dropdownWidth : placeholderWidth + SELECT_WIDTH_OFFSET_RIGHT;
const aValue = value != undefined ? value+"" : undefined;
+ const customSelectProps = omit(customProps, [""]);
return (
);
diff --git a/modules/components/widgets/antd/value/TextArea.jsx b/modules/components/widgets/antd/value/TextArea.jsx
index f077c3271..5f8ac77b5 100644
--- a/modules/components/widgets/antd/value/TextArea.jsx
+++ b/modules/components/widgets/antd/value/TextArea.jsx
@@ -2,7 +2,6 @@ import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import { Input, Col } from "antd";
const { TextArea } = Input;
-const defaultMaxRows = 5;
export default class TextAreaWidget extends PureComponent {
static propTypes = {
@@ -25,7 +24,7 @@ export default class TextAreaWidget extends PureComponent {
render() {
const {config, placeholder, customProps, value, readonly, maxLength, maxRows, fullWidth} = this.props;
- const {renderSize} = config.settings;
+ const {renderSize, defaultMaxRows} = config.settings;
const aValue = value != undefined ? value : null;
return (
diff --git a/modules/components/widgets/material/value/MaterialAutocomplete.jsx b/modules/components/widgets/material/value/MaterialAutocomplete.jsx
index a44495204..f93d9d919 100644
--- a/modules/components/widgets/material/value/MaterialAutocomplete.jsx
+++ b/modules/components/widgets/material/value/MaterialAutocomplete.jsx
@@ -4,17 +4,24 @@ import TextField from "@material-ui/core/TextField";
import FormControl from "@material-ui/core/FormControl";
import Autocomplete, { createFilterOptions } from "@material-ui/lab/Autocomplete";
import CircularProgress from "@material-ui/core/CircularProgress";
+import Chip from "@material-ui/core/Chip";
+import Checkbox from "@material-ui/core/Checkbox";
+import { makeStyles } from "@material-ui/core/styles";
+import CheckBoxOutlineBlankIcon from "@material-ui/icons/CheckBoxOutlineBlank";
+import CheckBoxIcon from "@material-ui/icons/CheckBox";
import useListValuesAutocomplete from "../../../../hooks/useListValuesAutocomplete";
+const nonCheckedIcon = ;
+const checkedIcon = ;
const defaultFilterOptions = createFilterOptions();
+const emptyArray = [];
export default (props) => {
const {
- allowCustomValues,
+ allowCustomValues, multiple,
value: selectedValue, customProps, readonly, config
} = props;
- const hasValue = selectedValue != null;
// hook
const {
@@ -33,22 +40,58 @@ export default (props) => {
getOptionDisabled,
getOptionLabel,
} = useListValuesAutocomplete(props, {
- debounceTimeout: 100
+ debounceTimeout: 100,
+ multiple
});
// setings
- const {defaultSliderWidth} = config.settings;
- const {width, ...rest} = customProps || {};
- const customInputProps = rest.input || {};
- const customAutocompleteProps = omit(rest.autocomplete || rest, ["showSearch"]);
+ const {defaultSelectWidth, defaultSearchWidth} = config.settings;
+ const {width, showCheckboxes, ...rest} = customProps || {};
+ let customInputProps = rest.input || {};
+ const inputWidth = customInputProps.width || defaultSearchWidth;
+ customInputProps = omit(customInputProps, ["width"]);
+ const customAutocompleteProps = omit(rest, ["showSearch", "showCheckboxes"]);
+ const fullWidth = true;
+ const minWidth = width || defaultSelectWidth;
+ const style = {
+ width: (multiple ? undefined : minWidth),
+ minWidth: minWidth
+ };
+ const placeholder = !readonly ? aPlaceholder : "";
+ const hasValue = selectedValue != null;
+ // should be simple value to prevent re-render!s
+ const value = hasValue ? selectedValue : (multiple ? emptyArray : null);
+
const filterOptions = (options, params) => {
const filtered = defaultFilterOptions(options, params);
const extended = extendOptions(filtered, params);
return extended;
};
- // Render
+ // styles
+ const useStyles = makeStyles((theme) => ({
+ // fix too small width
+ input: {
+ minWidth: inputWidth + " !important",
+ }
+ }));
+
+ const useStylesChip = makeStyles((theme) => ({
+ // fix height
+ root: {
+ height: "auto"
+ },
+ label: {
+ marginTop: "3px",
+ marginBottom: "3px",
+ }
+ }));
+
+ const classesChip = useStylesChip();
+ const classes = useStyles();
+
+ // render
const renderInput = (params) => {
return (
{
),
}}
disabled={readonly}
- placeholder={!readonly ? aPlaceholder : ""}
+ placeholder={placeholder}
//onChange={onInputChange}
{...customInputProps}
/>
);
};
+ const renderTags = (value, getTagProps) => value.map((option, index) => {
+ return ;
+ });
+
+ const renderOption = (option, { selected }) => {
+ if (multiple && showCheckboxes != false) {
+ return
+
+ {option.title}
+ ;
+ } else {
+ return {option.title};
+ }
+ };
+
return (
-
+
{
onClose={onClose}
inputValue={inputValue}
onInputChange={onInputChange}
- label={!readonly ? aPlaceholder : ""}
+ label={placeholder}
onChange={onChange}
- value={hasValue ? selectedValue : null} // should be simple value to prevent re-render!
+ value={value}
getOptionSelected={getOptionSelected}
disabled={readonly}
readOnly={readonly}
@@ -93,6 +164,8 @@ export default (props) => {
getOptionLabel={getOptionLabel}
getOptionDisabled={getOptionDisabled}
renderInput={renderInput}
+ renderTags={renderTags}
+ renderOption={renderOption}
filterOptions={filterOptions}
{...customAutocompleteProps}
>
diff --git a/modules/components/widgets/material/value/MaterialMultiSelect.jsx b/modules/components/widgets/material/value/MaterialMultiSelect.jsx
index fb72891c8..bc0135296 100644
--- a/modules/components/widgets/material/value/MaterialMultiSelect.jsx
+++ b/modules/components/widgets/material/value/MaterialMultiSelect.jsx
@@ -46,7 +46,7 @@ export default ({listValues, value, setValue, allowCustomValues, readonly, place
disabled={readonly}
readOnly={readonly}
renderValue={renderValue}
- {...omit(customProps, ["showSearch"])}
+ {...omit(customProps, ["showSearch", "input", "showCheckboxes"])}
>
{renderOptions(hasValue ? value : [])}
diff --git a/modules/components/widgets/material/value/MaterialSelect.jsx b/modules/components/widgets/material/value/MaterialSelect.jsx
index d758b24d0..0ff95d69e 100644
--- a/modules/components/widgets/material/value/MaterialSelect.jsx
+++ b/modules/components/widgets/material/value/MaterialSelect.jsx
@@ -43,7 +43,7 @@ export default ({listValues, value, setValue, allowCustomValues, readonly, place
disabled={readonly}
readOnly={readonly}
renderValue={renderValue}
- {...omit(customProps, ["showSearch"])}
+ {...omit(customProps, ["showSearch", "input"])}
>
{renderOptions()}
diff --git a/modules/components/widgets/material/value/MaterialTextArea.jsx b/modules/components/widgets/material/value/MaterialTextArea.jsx
index e73cacb45..510ba1df1 100644
--- a/modules/components/widgets/material/value/MaterialTextArea.jsx
+++ b/modules/components/widgets/material/value/MaterialTextArea.jsx
@@ -1,10 +1,10 @@
import React from "react";
import TextField from "@material-ui/core/TextField";
import FormControl from "@material-ui/core/FormControl";
-const defaultMaxRows = 5;
export default (props) => {
const {value, setValue, config, readonly, placeholder, customProps, maxLength, maxRows, fullWidth} = props;
+ const {defaultMaxRows} = config.settings;
const onChange = e => {
let val = e.target.value;
diff --git a/modules/config/basic.js b/modules/config/basic.js
index 925a75de8..62e4b2131 100644
--- a/modules/config/basic.js
+++ b/modules/config/basic.js
@@ -1031,7 +1031,10 @@ const settings = {
showSearch: true
},
- defaultSliderWidth: "200px"
+ defaultSliderWidth: "200px",
+ defaultSelectWidth: "200px",
+ defaultSearchWidth: "100px",
+ defaultMaxRows: 5,
};
//----------------------------
diff --git a/modules/config/material/index.js b/modules/config/material/index.js
index 3c6da9a50..9b5c508c3 100644
--- a/modules/config/material/index.js
+++ b/modules/config/material/index.js
@@ -61,7 +61,11 @@ const widgets = {
},
multiselect: {
...BasicConfig.widgets.multiselect,
- factory: (props) => ,
+ factory: (props) => {
+ return (props.asyncFetch || props.showSearch)
+ ?
+ : ;
+ },
},
select: {
...BasicConfig.widgets.select,
diff --git a/modules/hooks/useListValuesAutocomplete.jsx b/modules/hooks/useListValuesAutocomplete.jsx
index 2154bb922..8c228f2c1 100644
--- a/modules/hooks/useListValuesAutocomplete.jsx
+++ b/modules/hooks/useListValuesAutocomplete.jsx
@@ -10,7 +10,8 @@ const useListValuesAutocomplete = ({
listValues: staticListValues, allowCustomValues,
value: selectedValue, setValue, placeholder
}, {
- debounceTimeout
+ debounceTimeout,
+ multiple
}) => {
const loadMoreTitle = "Load more...";
const loadingMoreTitle = "Loading more...";
@@ -45,8 +46,8 @@ const useListValuesAutocomplete = ({
const canShowLoadMore = !isLoading && canLoadMore;
const options = mapListValues(listValues, listValueToOption);
const hasValue = selectedValue != null;
- const selectedListValue = hasValue ? getListValue(selectedValue, listValues) : null;
- const selectedOption = listValueToOption(selectedListValue);
+ // const selectedListValue = hasValue ? getListValue(selectedValue, listValues) : null;
+ // const selectedOption = listValueToOption(selectedListValue);
// fetch
const fetchListValues = async (filter = null, isLoadMore = false) => {
@@ -145,7 +146,18 @@ const useListValuesAutocomplete = ({
} else if (option && option.specialValue == "LOADING_MORE") {
isSelectedLoadMore.current = true;
} else {
- setValue(option == null ? undefined : option.value, [option]);
+ if (multiple) {
+ let newSelectedListValues = option.map( o =>
+ o.value != null ? o : getListValue(o, listValues)
+ );
+ let newSelectedValues = newSelectedListValues.map(o => o.value);
+ if (!newSelectedValues.length)
+ newSelectedValues = undefined; //not allow []
+ setValue(newSelectedValues, newSelectedListValues);
+ } else {
+ const v = option == null ? undefined : option.value;
+ setValue(v, [option]);
+ }
}
};
@@ -153,14 +165,18 @@ const useListValuesAutocomplete = ({
const val = newInputValue;
//const isTypeToSearch = e.type == 'change';
- if (val === loadMoreTitle || val == loadingMoreTitle) {
+ if (val === loadMoreTitle || val === loadingMoreTitle) {
return;
}
setInputValue(val);
if (allowCustomValues) {
- setValue(val, [val]);
+ if (multiple) {
+ //todo
+ } else {
+ setValue(val, [val]);
+ }
}
const canSearchAsync = useAsyncSearch && (forceAsyncSearch ? !!val : true);
@@ -238,15 +254,15 @@ const useListValuesAutocomplete = ({
isInitialLoading,
isLoading,
isLoadingMore,
-
+
extendOptions,
getOptionSelected,
getOptionDisabled,
getOptionLabel,
// unused
- selectedListValue,
- selectedOption,
+ //selectedListValue,
+ //selectedOption,
aPlaceholder,
};
};
diff --git a/modules/index.d.ts b/modules/index.d.ts
index 3c7934adc..d212daa28 100644
--- a/modules/index.d.ts
+++ b/modules/index.d.ts
@@ -443,6 +443,7 @@ export interface SelectFieldSettings extends BasicFieldSettings {
listValues?: ListValues,
allowCustomValues?: boolean,
showSearch?: boolean,
+ showCheckboxes?: boolean,
asyncFetch?: AsyncFetchListValuesFn,
useLoadMore?: boolean,
useAsyncSearch?: boolean,
@@ -619,6 +620,9 @@ export interface RenderSettings {
renderAfterActions?: Factory,
renderRuleError?: Factory,
defaultSliderWidth?: string,
+ defaultSelectWidth?: string,
+ defaultSearchWidth?: string,
+ defaultMaxRows?: number,
}
export interface BehaviourSettings {
diff --git a/package.json b/package.json
index 025255865..7ca919b01 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-awesome-query-builder",
- "version": "4.4.1",
+ "version": "4.4.2",
"description": "User-friendly query builder for React. Demo: https://ukrbublik.github.io/react-awesome-query-builder",
"keywords": [
"query-builder",