Skip to content

Commit f13acf6

Browse files
yordan-stgjulivan
authored andcommitted
feat: add onChangeFilterInputEvent to combobox for filter input handling
1 parent 8eccb4e commit f13acf6

8 files changed

Lines changed: 78 additions & 6 deletions

File tree

packages/pluggableWidgets/combobox-web/src/Combobox.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,14 @@
329329
<caption>On leave action</caption>
330330
<description />
331331
</property>
332+
333+
<property key="onChangeFilterInputEvent" type="action" required="false">
334+
<caption>On change filter input</caption>
335+
<description />
336+
<actionVariables>
337+
<actionVariable key="filterInput" caption="Filter Input" type="String" />
338+
</actionVariables>
339+
</property>
332340
</propertyGroup>
333341
<propertyGroup caption="Accessibility">
334342
<propertyGroup caption="Accessibility">

packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function MultiSelection({
3333
items,
3434
setSelectedItems,
3535
toggleSelectedItem
36-
} = useDownshiftMultiSelectProps(selector, options, a11yConfig.a11yStatusMessage);
36+
} = useDownshiftMultiSelectProps(selector, { ...options }, a11yConfig.a11yStatusMessage);
3737
const inputRef = useRef<HTMLInputElement>(null);
3838
const isSelectedItemsBoxStyle = selector.selectedItemsStyle === "boxes";
3939
const isOptionsSelected = selector.isOptionsSelected();

packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function SingleSelection({
2727
reset,
2828
isOpen,
2929
highlightedIndex
30-
} = useDownshiftSingleSelectProps(selector, options, a11yConfig.a11yStatusMessage);
30+
} = useDownshiftSingleSelectProps(selector, { ...options }, a11yConfig.a11yStatusMessage);
3131
const inputRef = useRef<HTMLInputElement>(null);
3232
const lazyLoading = selector.lazyLoading ?? false;
3333
const { onScroll } = useLazyLoading({

packages/pluggableWidgets/combobox-web/src/helpers/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ interface SelectorBase<T, V> {
7979

8080
onEnterEvent?: () => void;
8181
onLeaveEvent?: () => void;
82+
onFilterInputChange?: (filterValue: string) => void;
8283
}
8384

8485
export interface SingleSelector extends SelectorBase<"single", string> {}
@@ -101,6 +102,7 @@ export interface SelectionBaseProps<Selector> {
101102
tabIndex: number;
102103
ariaRequired: DynamicValue<boolean>;
103104
ariaLabel?: string;
105+
onFilterInputChange?: (filterValue: string) => void;
104106
a11yConfig: {
105107
ariaLabels: {
106108
clearSelection: string;

packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftMultiSelectProps.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,18 @@ function useComboboxProps(
148148
selectedItem: null,
149149
inputId: options?.inputId,
150150
labelId: options?.labelId,
151-
onInputValueChange({ inputValue }) {
151+
onInputValueChange({ inputValue, type }) {
152152
selector.options.setSearchTerm(inputValue!);
153+
154+
if (
155+
selector.onFilterInputChange &&
156+
type &&
157+
(type === "__input_change__" || type.includes("input_change")) &&
158+
inputValue &&
159+
inputValue.trim().length > 0
160+
) {
161+
selector.onFilterInputChange(inputValue!);
162+
}
153163
},
154164
getA11yStatusMessage(options) {
155165
let message =

packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftSingleSelectProps.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,18 @@ export function useDownshiftSingleSelectProps(
2929
onSelectedItemChange({ selectedItem }: UseComboboxStateChange<string>) {
3030
selector.setValue(selectedItem ?? null);
3131
},
32-
onInputValueChange({ inputValue }) {
32+
onInputValueChange({ inputValue, type }) {
3333
selector.options.setSearchTerm(inputValue!);
34+
35+
if (
36+
selector.onFilterInputChange &&
37+
type &&
38+
(type === "__input_change__" || type.includes("input_change")) &&
39+
inputValue &&
40+
inputValue.trim().length > 0
41+
) {
42+
selector.onFilterInputChange(inputValue!);
43+
}
3444
},
3545
getA11yStatusMessage(options) {
3646
const selectedItem = selector.caption.get(selector.currentId);
Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,55 @@
1-
import { useRef, useState } from "react";
1+
import { useRef, useState, useCallback } from "react";
22
import { ComboboxContainerProps } from "../../typings/ComboboxProps";
33
import { getSelector } from "../helpers/getSelector";
44
import { Selector } from "../helpers/types";
55

66
export function useGetSelector(props: ComboboxContainerProps): Selector {
77
const selectorRef = useRef<Selector | undefined>(undefined);
88
const [, setInput] = useState({});
9+
const debounceTimeoutRef = useRef<NodeJS.Timeout>();
10+
const lastExecutedValueRef = useRef<string>("");
11+
12+
const onFilterInputChange = useCallback(
13+
(filterValue: string) => {
14+
if (!props.onChangeFilterInputEvent) {
15+
return;
16+
}
17+
18+
if (lastExecutedValueRef.current === filterValue) {
19+
return;
20+
}
21+
22+
if (debounceTimeoutRef.current) {
23+
clearTimeout(debounceTimeoutRef.current);
24+
}
25+
26+
debounceTimeoutRef.current = setTimeout(() => {
27+
lastExecutedValueRef.current = filterValue;
28+
if (props.onChangeFilterInputEvent?.canExecute && !props.onChangeFilterInputEvent?.isExecuting) {
29+
props.onChangeFilterInputEvent.execute({
30+
filterInput: filterValue
31+
});
32+
}
33+
}, 300);
34+
},
35+
[props.onChangeFilterInputEvent]
36+
);
37+
938
if (!selectorRef.current) {
1039
selectorRef.current = getSelector(props);
1140
selectorRef.current.options.onAfterSearchTermChange(() => setInput({}));
41+
42+
if (props.onChangeFilterInputEvent) {
43+
selectorRef.current.onFilterInputChange = onFilterInputChange;
44+
}
1245
}
1346
selectorRef.current.updateProps(props);
47+
48+
if (props.onChangeFilterInputEvent) {
49+
selectorRef.current.onFilterInputChange = onFilterInputChange;
50+
} else {
51+
selectorRef.current.onFilterInputChange = undefined;
52+
}
53+
1454
return selectorRef.current;
1555
}

packages/pluggableWidgets/combobox-web/typings/ComboboxProps.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* @author Mendix Widgets Framework Team
55
*/
66
import { ComponentType, ReactNode } from "react";
7-
import { ActionValue, DynamicValue, EditableValue, ListValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ReferenceValue, ReferenceSetValue, SelectionSingleValue, SelectionMultiValue } from "mendix";
7+
import { ActionValue, DynamicValue, EditableValue, ListValue, Option, ListAttributeValue, ListExpressionValue, ListWidgetValue, ReferenceValue, ReferenceSetValue, SelectionSingleValue, SelectionMultiValue } from "mendix";
88
import { Big } from "big.js";
99

1010
export type SourceEnum = "context" | "database" | "static";
@@ -89,6 +89,7 @@ export interface ComboboxContainerProps {
8989
onChangeEvent?: ActionValue;
9090
onEnterEvent?: ActionValue;
9191
onLeaveEvent?: ActionValue;
92+
onChangeFilterInputEvent?: ActionValue<{ filterInput: Option<string> }>;
9293
ariaRequired: DynamicValue<boolean>;
9394
ariaLabel?: DynamicValue<string>;
9495
clearButtonAriaLabel?: DynamicValue<string>;
@@ -145,6 +146,7 @@ export interface ComboboxPreviewProps {
145146
onChangeDatabaseEvent: {} | null;
146147
onEnterEvent: {} | null;
147148
onLeaveEvent: {} | null;
149+
onChangeFilterInputEvent: {} | null;
148150
ariaRequired: string;
149151
ariaLabel: string;
150152
clearButtonAriaLabel: string;

0 commit comments

Comments
 (0)