Skip to content

Commit 0daf399

Browse files
authored
Added customOperatorSelectProps (#1098)
* add customOperatorSelectProps (issue #1094) . * polish fluent select * chlog * devcontainer
1 parent 39da010 commit 0daf399

File tree

20 files changed

+147
-59
lines changed

20 files changed

+147
-59
lines changed

.codesandbox/tasks.json

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
{
2+
"$schema": "https://codesandbox.io/schemas/tasks.json",
3+
24
// These tasks will run in order when initializing your CodeSandbox project.
35
"setupTasks": [
4-
// {
5-
// "name": "Install pnpm 9",
6-
// "command": "npm i -g pnpm@9"
7-
// },
6+
{
7+
"name": "Install pnpm 8",
8+
"command": "npm i -g pnpm@8"
9+
},
810
{
911
"name": "Install dependencies",
1012
"command": "pnpm install --frozen-lockfile"
@@ -25,6 +27,22 @@
2527
"port": 3001
2628
}
2729
},
30+
"install": {
31+
"name": "install",
32+
"command": "pnpm install"
33+
},
34+
"install-noscripts": {
35+
"name": "install-noscripts",
36+
"command": "pnpm install-noscripts"
37+
},
38+
"install-frozen": {
39+
"name": "install-frozen",
40+
"command": "pnpm install-frozen"
41+
},
42+
"install-frozen-noscripts": {
43+
"name": "install-frozen-noscripts",
44+
"command": "pnpm install-frozen-noscripts"
45+
},
2846
"test": {
2947
"name": "test",
3048
"command": "pnpm test"

.devcontainer/devcontainer.json

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
{
2-
"image": "mcr.microsoft.com/devcontainers/universal:2",
2+
"image": "mcr.microsoft.com/devcontainers/javascript-node:18",
3+
// "image": "mcr.microsoft.com/devcontainers/universal:2",
34
"features": {
5+
"ghcr.io/kreemer/features/chrometesting:1": {
6+
"version": "latest"
7+
}
48
},
5-
9+
610
"customizations": {
711
"codespaces": {
8-
"openFiles": [
9-
"README.md",
10-
"packages/examples/src/demo/index.tsx"
11-
]
12+
"openFiles": ["packages/examples/src/demo/index.tsx"]
1213
}
1314
},
14-
15+
1516
"postCreateCommand": "./scripts/nvms.sh pnpm install",
1617
"postAttachCommand": {
1718
"server": "./scripts/nvms.sh pnpm start"
1819
},
1920

20-
"forwardPorts": [
21-
3001,
22-
5174,
23-
5175,
24-
3002
25-
],
21+
"forwardPorts": [3001, 5174, 5175, 3002],
2622
"portsAttributes": {
2723
"3001": {
2824
"label": "Examples app",

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- Added tasks for VSCode (PR #1095)
77
- Refactored examples demo app (PR #1097)
88
- JsonLogic roundtrip fix (issue #1084) (PR #1092)
9+
- Fixed lagging state of multiselect FluentUI component and added searchable options to select and multiselect (PR #1090)
10+
- Added `customOperatorSelectProps` (issue #1094) (PR #1098)
911
- 6.6.2
1012
- Fixed issue with rendering func select inside func (PR #1086) (issue #1085)
1113
- Added `DefaultUtils` typings to `index.d.ts` (PR #1078) (issue #1079)

CONFIG.adoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ Render settings:
384384
|showLabels |false |Show labels above all fields?
385385
|maxLabelsLength |100 |To shorten long labels of fields/values (by length, i.e. number of chars)
386386
|dropdownPlacement |`bottomLeft` |Placement of antdesign's https://ant.design/components/dropdown/[dropdown] pop-up menu
387-
|customFieldSelectProps |`{}` |You can pass props to `FieldSelect` widget. Example: `{showSearch: true}`
387+
|customFieldSelectProps |`{}` |You can pass props to the field select widget. Example: `{showSearch: true}`
388+
|customOperatorSelectProps |`{}` |You can pass props to the operator select widget. Example: `{showSearch: true}`
388389
|groupActionsPosition |`topRight` |You can change the position of the group actions to the following: +
389390
`topLeft, topCenter, topRight, bottomLeft, bottomCenter, bottomRight`
390391
|renderBeforeWidget | |

packages/antd/modules/widgets/core/FieldSelect.jsx

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from "react";
1+
import React, { useState, useCallback, useMemo } from "react";
22
import { Tooltip, Select } from "antd";
33
import {BUILT_IN_PLACEMENTS, SELECT_WIDTH_OFFSET_RIGHT, calcTextWidth} from "../../utils/domUtils";
44
const { Option, OptGroup } = Select;
@@ -34,23 +34,25 @@ const FieldSelect = (props) => {
3434
if (tooltipText == selectedLabel)
3535
tooltipText = null;
3636

37-
const onChange = (key) => {
37+
const style = useMemo(() => ({ width }), [width]);
38+
39+
const onChange = useCallback((key) => {
3840
setField(key);
39-
};
41+
}, [setField]);
4042

41-
const onSearch = (search) => {
43+
const onSearch = useCallback((search) => {
4244
setSearchValue(search);
43-
};
45+
}, [setSearchValue]);
4446

45-
const filterOption = (input, option) => {
47+
const filterOption = useCallback((input, option) => {
4648
const keysForFilter = config.settings.fieldItemKeysForSearch
4749
.map(k => mapFieldItemToOptionKeys[k]);
4850
const valueForFilter = keysForFilter
4951
.map(k => (typeof option[k] == "string" ? option[k] : ""))
5052
.join("\0");
5153
const matches = valueForFilter.toLowerCase().indexOf(input.toLowerCase()) >= 0;
5254
return matches;
53-
};
55+
}, [config.settings.fieldItemKeysForSearch]);
5456

5557
const renderSelectItems = (fields, level = 0) => {
5658
return fields.map(field => {
@@ -107,7 +109,7 @@ const FieldSelect = (props) => {
107109
onDropdownVisibleChange={setOpen}
108110
dropdownAlign={dropdownAlign}
109111
popupMatchSelectWidth={false}
110-
style={{ width }}
112+
style={style}
111113
placeholder={placeholder}
112114
size={config.settings.renderSize}
113115
onChange={onChange}
@@ -116,9 +118,9 @@ const FieldSelect = (props) => {
116118
filterOption={filterOption}
117119
disabled={readonly}
118120
status={errorText && "error"}
119-
showSearch={true}
121+
showSearch={!!showSearch}
120122
searchValue={searchValue}
121-
onSearch={onSearch}
123+
onSearch={showSearch ? onSearch : undefined}
122124
{...customProps}
123125
>{fieldSelectItems}</Select>
124126
);

packages/antd/modules/widgets/value/Range.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export default class RangeWidget extends PureComponent {
126126
max={max}
127127
step={step}
128128
marks={marks}
129-
included={false}
129+
included={true}
130130
range={true}
131131
onChange={this.handleChange}
132132
{...customSliderProps}

packages/core/modules/index.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,7 @@ export type AsyncFetchListValuesFn = (search: string | null, offset: number) =>
11521152

11531153
export interface BasicFieldSettings<V = RuleValue> {
11541154
validateValue?: ValidateValue<V> | SerializedFunction;
1155+
valuePlaceholder?: string;
11551156
}
11561157
export interface TextFieldSettings<V = string> extends BasicFieldSettings<V> {
11571158
maxLength?: number;
@@ -1174,6 +1175,7 @@ export interface SelectFieldSettings<V = string | number> extends BasicFieldSett
11741175
listValues?: ListValues;
11751176
allowCustomValues?: boolean;
11761177
showSearch?: boolean;
1178+
searchPlaceholder?: string;
11771179
showCheckboxes?: boolean;
11781180
asyncFetch?: AsyncFetchListValuesFn | SerializedFunction;
11791181
useLoadMore?: boolean;

packages/examples/src/demo/config/index.tsx

+18
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@ export default (skin: string) => {
287287
customFieldSelectProps: {
288288
showSearch: true
289289
},
290+
customOperatorSelectProps: {
291+
// showSearch: true
292+
},
290293
// renderField: (props) => <FieldCascader {...props} />,
291294
// renderOperator: (props) => <FieldDropdown {...props} />,
292295
// renderFunc: (props) => <FieldSelect {...props} />,
@@ -520,8 +523,23 @@ export default (skin: string) => {
520523
label: "Color",
521524
type: "select",
522525
valueSources: ["value"],
526+
widgets: {
527+
select: {
528+
widgetProps: {
529+
valuePlaceholder: "Select color",
530+
searchPlaceholder: "Search color",
531+
},
532+
},
533+
multiselect: {
534+
widgetProps: {
535+
valuePlaceholder: "Select colors",
536+
searchPlaceholder: "Search colors",
537+
},
538+
},
539+
},
523540
fieldSettings: {
524541
showSearch: true,
542+
525543
// * old format:
526544
// listValues: {
527545
// yellow: 'Yellow',

packages/examples/src/demo/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import { defaultInitFile, initialSkin, validationTranslateOptions, defaultRender
1717
import "./i18n";
1818

1919
// Load config and initial tree
20-
const loadedConfig = loadConfig(initialSkin);
21-
const initTree = initTreeWithValidation(defaultInitFile, loadedConfig, validationTranslateOptions);
20+
const loadedConfig = loadConfig(window._initialSkin || initialSkin);
21+
const initTree = initTreeWithValidation(window._initFile || defaultInitFile, loadedConfig, validationTranslateOptions);
2222

2323
// Trick for HMR: triggers callback put in useHmrUpdate on every update from HMR
2424
dispatchHmrUpdate(loadedConfig, initTree);

packages/examples/src/demo/options.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import {
33
} from "@react-awesome-query-builder/ui";
44
import { DemoQueryBuilderState } from "./types";
55

6-
export const defaultInitFile = window._initFile || "tree/complex";
7-
export const initialSkin = window._initialSkin || "mui";
6+
export const defaultInitFile = "tree/complex";
7+
export const initialSkin = "mui";
88

99
export const validationTranslateOptions: Partial<SanitizeOptions> = {
1010
translateErrors: true,

packages/fluent/modules/widgets/SearchableDropdown.jsx

+28-15
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
1-
import React, { useState } from "react";
1+
import React, { useState, useCallback, useMemo } from "react";
22
import { Dropdown, DropdownMenuItemType, SearchBox } from "@fluentui/react";
33

44
// Credits for this code belong to: https://github.com/microsoft/fluentui/issues/16566#issuecomment-853933969
55
const SearchableDropdown = (props) => {
6+
const {options, customProps, searchProps, ...dropdownProps} = props;
7+
const {searchProps: customSearchProps, ...customDropdownProps} = customProps ?? {};
8+
69
const [searchText, setSearchText] = useState("");
710

8-
function renderOption(option) {
11+
const onSearchChange = useCallback((ev, newValue) => setSearchText(newValue), [setSearchText]);
12+
13+
const calloutProps = { shouldRestoreFocus: false, setInitialFocus: false }; //not working
14+
15+
const renderOption = useCallback((option) => {
916
return (option.itemType === DropdownMenuItemType.Header
1017
&& option.key === "FilterHeader")
1118
? <SearchBox
12-
onChange={(ev, newValue) => setSearchText(newValue)}
19+
onChange={onSearchChange}
1320
underlined={true}
14-
placeholder="Search options"
21+
placeholder={searchProps?.placeholder}
22+
{...(customSearchProps ?? {})}
1523
/>
1624
: <>{option.text}</>;
17-
}
25+
}, [SearchBox, onSearchChange, customSearchProps, searchProps]);
26+
27+
const onDismiss = useCallback(() => setSearchText(""), [setSearchText]);
28+
29+
const dropdownOptions = useMemo(() => ([
30+
{ key: "FilterHeader", text: "-", itemType: DropdownMenuItemType.Header },
31+
{ key: "divider_filterHeader", text: "-", itemType: DropdownMenuItemType.Divider },
32+
...options.map(option => !option.disabled && option.text.toLowerCase().indexOf(searchText.toLowerCase()) > -1
33+
? option : { ...option, hidden: true }
34+
),
35+
]), [options, searchText]);
1836

1937
return (
2038
<Dropdown
21-
{...props}
22-
options={[
23-
{ key: "FilterHeader", text: "-", itemType: DropdownMenuItemType.Header },
24-
{ key: "divider_filterHeader", text: "-", itemType: DropdownMenuItemType.Divider },
25-
...props.options.map(option => !option.disabled && option.text.toLowerCase().indexOf(searchText.toLowerCase()) > -1
26-
? option : { ...option, hidden: true }
27-
),
28-
]}
29-
calloutProps={{ shouldRestoreFocus: false, setInitialFocus: false }} //not working
39+
options={dropdownOptions}
40+
calloutProps={calloutProps}
3041
onRenderOption={renderOption}
31-
onDismiss={() => setSearchText("")}
42+
onDismiss={onDismiss}
43+
{...dropdownProps}
44+
{...customDropdownProps}
3245
/>
3346
);
3447
};

packages/fluent/modules/widgets/value/FluentUIMultiSelect.jsx

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useCallback, useMemo } from "react";
22
import { Dropdown } from "@fluentui/react";
33
import { Utils } from "@react-awesome-query-builder/ui";
44
import SearchableDropdown from "../SearchableDropdown";
@@ -11,15 +11,17 @@ export default ({
1111
setValue,
1212
readonly,
1313
placeholder,
14+
searchPlaceholder,
1415
showSearch,
16+
customProps,
1517
}) => {
1618

1719
const renderOptions = () =>
1820
mapListValues(listValues, ({ title, value }) => {
1921
return { key: value, text: title };
2022
});
2123

22-
const onChange = (_, item) => {
24+
const onChange = useCallback((_, item) => {
2325
if (item) {
2426
const currentItems = value ?? [];
2527
const selectedItems = item.selected
@@ -28,10 +30,19 @@ export default ({
2830

2931
setValue(selectedItems);
3032
}
31-
};
33+
}, [value, setValue]);
3234

3335
const DropdownType = showSearch ? SearchableDropdown : Dropdown;
3436

37+
const searchProps = useMemo(() => ({
38+
placeholder: searchPlaceholder, // || "Search options"
39+
}), [searchPlaceholder]);
40+
41+
const otherProps = {
42+
...(customProps ?? {}),
43+
...(showSearch ? {searchProps} : {}),
44+
};
45+
3546
return (
3647
<DropdownType
3748
placeholder={placeholder || "Select options"}
@@ -41,6 +52,7 @@ export default ({
4152
multiSelect
4253
options={renderOptions()}
4354
disabled={readonly}
55+
{...otherProps}
4456
/>
4557
);
4658
};

0 commit comments

Comments
 (0)