diff --git a/packages/pluggableWidgets/combobox-web/CHANGELOG.md b/packages/pluggableWidgets/combobox-web/CHANGELOG.md
index 2d993ae99d..487537f839 100644
--- a/packages/pluggableWidgets/combobox-web/CHANGELOG.md
+++ b/packages/pluggableWidgets/combobox-web/CHANGELOG.md
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
+### Added
+
+- We added a new "On change filter input" event that triggers when users type in the combobox filter field, passing the current filter text as an action variable to enable custom nanoflows/microflows for dynamic filtering scenarios.
+
## [2.4.3] - 2025-07-22
### Fixed
diff --git a/packages/pluggableWidgets/combobox-web/package.json b/packages/pluggableWidgets/combobox-web/package.json
index f0e229cd9d..557ad8927e 100644
--- a/packages/pluggableWidgets/combobox-web/package.json
+++ b/packages/pluggableWidgets/combobox-web/package.json
@@ -20,7 +20,7 @@
},
"packagePath": "com.mendix.widget.web",
"marketplace": {
- "minimumMXVersion": "10.7.0",
+ "minimumMXVersion": "10.22.0",
"appNumber": 219304,
"appName": "Combo box",
"reactReady": true
diff --git a/packages/pluggableWidgets/combobox-web/src/Combobox.xml b/packages/pluggableWidgets/combobox-web/src/Combobox.xml
index 5f87880ab5..a67eda1303 100644
--- a/packages/pluggableWidgets/combobox-web/src/Combobox.xml
+++ b/packages/pluggableWidgets/combobox-web/src/Combobox.xml
@@ -329,6 +329,14 @@
On leave action
+
+
+ On change filter input
+
+
+
+
+
diff --git a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx
index 7e586ba40d..a818484653 100644
--- a/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx
+++ b/packages/pluggableWidgets/combobox-web/src/components/MultiSelection/MultiSelection.tsx
@@ -16,7 +16,11 @@ export function MultiSelection({
a11yConfig,
menuFooterContent,
ariaRequired,
- ...options
+ labelId,
+ inputId,
+ ariaLabel,
+ readOnlyStyle,
+ noOptionsText
}: SelectionBaseProps): ReactElement {
const {
isOpen,
@@ -33,11 +37,11 @@ export function MultiSelection({
items,
setSelectedItems,
toggleSelectedItem
- } = useDownshiftMultiSelectProps(selector, options, a11yConfig.a11yStatusMessage);
+ } = useDownshiftMultiSelectProps(selector, { inputId, labelId }, a11yConfig.a11yStatusMessage);
const inputRef = useRef(null);
const isSelectedItemsBoxStyle = selector.selectedItemsStyle === "boxes";
const isOptionsSelected = selector.isOptionsSelected();
- const inputLabel = getInputLabel(options.inputId);
+ const inputLabel = getInputLabel(inputId);
const hasLabel = useMemo(() => Boolean(inputLabel), [inputLabel]);
const inputProps = getInputProps({
...getDropdownProps(
@@ -65,7 +69,7 @@ export function MultiSelection({
disabled: selector.readOnly,
readOnly: selector.options.filterType === "none",
"aria-required": ariaRequired.value,
- "aria-label": !hasLabel && options.ariaLabel ? options.ariaLabel : undefined
+ "aria-label": !hasLabel && ariaLabel ? ariaLabel : undefined
});
const memoizedselectedCaptions = useMemo(
@@ -91,7 +95,7 @@ export function MultiSelection({
{
if (isOptionsSelected === "all") {
@@ -181,7 +185,7 @@ export function MultiSelection({
) : undefined
}
menuFooterContent={menuFooterContent}
- inputId={options.inputId}
+ inputId={inputId}
selector={selector}
isOpen={isOpen}
highlightedIndex={highlightedIndex}
@@ -189,7 +193,7 @@ export function MultiSelection({
getItemProps={getItemProps}
getMenuProps={getMenuProps}
selectedItems={selectedItems}
- noOptionsText={options.noOptionsText}
+ noOptionsText={noOptionsText}
onOptionClick={() => {
inputRef.current?.focus();
}}
diff --git a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx
index eccabce5b2..a8435a7af6 100644
--- a/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx
+++ b/packages/pluggableWidgets/combobox-web/src/components/SingleSelection/SingleSelection.tsx
@@ -27,7 +27,7 @@ export function SingleSelection({
reset,
isOpen,
highlightedIndex
- } = useDownshiftSingleSelectProps(selector, options, a11yConfig.a11yStatusMessage);
+ } = useDownshiftSingleSelectProps(selector, { ...options }, a11yConfig.a11yStatusMessage);
const inputRef = useRef(null);
const lazyLoading = selector.lazyLoading ?? false;
const { onScroll } = useLazyLoading({
diff --git a/packages/pluggableWidgets/combobox-web/src/helpers/types.ts b/packages/pluggableWidgets/combobox-web/src/helpers/types.ts
index edf5013642..a0cadbce34 100644
--- a/packages/pluggableWidgets/combobox-web/src/helpers/types.ts
+++ b/packages/pluggableWidgets/combobox-web/src/helpers/types.ts
@@ -79,6 +79,7 @@ interface SelectorBase {
onEnterEvent?: () => void;
onLeaveEvent?: () => void;
+ onFilterInputChange?: (filterValue: string) => void;
}
export interface SingleSelector extends SelectorBase<"single", string> {}
@@ -101,6 +102,7 @@ export interface SelectionBaseProps {
tabIndex: number;
ariaRequired: DynamicValue;
ariaLabel?: string;
+ onFilterInputChange?: (filterValue: string) => void;
a11yConfig: {
ariaLabels: {
clearSelection: string;
diff --git a/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftMultiSelectProps.ts b/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftMultiSelectProps.ts
index fbef975c0c..a7078240cd 100644
--- a/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftMultiSelectProps.ts
+++ b/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftMultiSelectProps.ts
@@ -148,8 +148,17 @@ function useComboboxProps(
selectedItem: null,
inputId: options?.inputId,
labelId: options?.labelId,
- onInputValueChange({ inputValue }) {
+ onInputValueChange({ inputValue, type }) {
selector.options.setSearchTerm(inputValue!);
+
+ if (
+ selector.onFilterInputChange &&
+ type?.includes("input_change") &&
+ inputValue &&
+ inputValue.trim()?.length > 0
+ ) {
+ selector.onFilterInputChange(inputValue);
+ }
},
getA11yStatusMessage(options) {
let message =
diff --git a/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftSingleSelectProps.ts b/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftSingleSelectProps.ts
index f65a2d9cbc..543d365061 100644
--- a/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftSingleSelectProps.ts
+++ b/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftSingleSelectProps.ts
@@ -29,8 +29,18 @@ export function useDownshiftSingleSelectProps(
onSelectedItemChange({ selectedItem }: UseComboboxStateChange) {
selector.setValue(selectedItem ?? null);
},
- onInputValueChange({ inputValue }) {
+ onInputValueChange({ inputValue, type }) {
selector.options.setSearchTerm(inputValue!);
+
+ if (
+ selector.onFilterInputChange &&
+ type &&
+ (type === "__input_change__" || type?.includes("input_change")) &&
+ inputValue &&
+ inputValue.trim().length > 0
+ ) {
+ selector.onFilterInputChange(inputValue!);
+ }
},
getA11yStatusMessage(options) {
const selectedItem = selector.caption.get(selector.currentId);
diff --git a/packages/pluggableWidgets/combobox-web/src/hooks/useGetSelector.ts b/packages/pluggableWidgets/combobox-web/src/hooks/useGetSelector.ts
index a0ac85d1a9..1118ced41b 100644
--- a/packages/pluggableWidgets/combobox-web/src/hooks/useGetSelector.ts
+++ b/packages/pluggableWidgets/combobox-web/src/hooks/useGetSelector.ts
@@ -1,4 +1,4 @@
-import { useRef, useState } from "react";
+import { useRef, useState, useCallback } from "react";
import { ComboboxContainerProps } from "../../typings/ComboboxProps";
import { getSelector } from "../helpers/getSelector";
import { Selector } from "../helpers/types";
@@ -6,10 +6,45 @@ import { Selector } from "../helpers/types";
export function useGetSelector(props: ComboboxContainerProps): Selector {
const selectorRef = useRef(undefined);
const [, setInput] = useState({});
+ const debounceTimeoutRef = useRef();
+ const lastExecutedValueRef = useRef("");
+
+ const onFilterInputChange = useCallback(
+ (filterValue: string) => {
+ if (!props.onChangeFilterInputEvent) {
+ return;
+ }
+
+ if (lastExecutedValueRef.current === filterValue) {
+ return;
+ }
+
+ if (debounceTimeoutRef.current) {
+ clearTimeout(debounceTimeoutRef.current);
+ }
+
+ debounceTimeoutRef.current = setTimeout(() => {
+ lastExecutedValueRef.current = filterValue;
+ try {
+ if (props.onChangeFilterInputEvent?.canExecute && !props.onChangeFilterInputEvent?.isExecuting) {
+ props.onChangeFilterInputEvent.execute({
+ filterInput: filterValue
+ });
+ }
+ } catch (error) {
+ console.error("Error executing onChangeFilterInputEvent:", error);
+ }
+ }, 300);
+ },
+ [props.onChangeFilterInputEvent]
+ );
+
if (!selectorRef.current) {
selectorRef.current = getSelector(props);
selectorRef.current.options.onAfterSearchTermChange(() => setInput({}));
}
selectorRef.current.updateProps(props);
+ selectorRef.current.onFilterInputChange = onFilterInputChange;
+
return selectorRef.current;
}
diff --git a/packages/pluggableWidgets/combobox-web/typings/ComboboxProps.d.ts b/packages/pluggableWidgets/combobox-web/typings/ComboboxProps.d.ts
index d23d3a96ac..80ba167b99 100644
--- a/packages/pluggableWidgets/combobox-web/typings/ComboboxProps.d.ts
+++ b/packages/pluggableWidgets/combobox-web/typings/ComboboxProps.d.ts
@@ -4,7 +4,7 @@
* @author Mendix Widgets Framework Team
*/
import { ComponentType, ReactNode } from "react";
-import { ActionValue, DynamicValue, EditableValue, ListValue, ListAttributeValue, ListExpressionValue, ListWidgetValue, ReferenceValue, ReferenceSetValue, SelectionSingleValue, SelectionMultiValue } from "mendix";
+import { ActionValue, DynamicValue, EditableValue, ListValue, Option, ListAttributeValue, ListExpressionValue, ListWidgetValue, ReferenceValue, ReferenceSetValue, SelectionSingleValue, SelectionMultiValue } from "mendix";
import { Big } from "big.js";
export type SourceEnum = "context" | "database" | "static";
@@ -89,6 +89,7 @@ export interface ComboboxContainerProps {
onChangeEvent?: ActionValue;
onEnterEvent?: ActionValue;
onLeaveEvent?: ActionValue;
+ onChangeFilterInputEvent?: ActionValue<{ filterInput: Option }>;
ariaRequired: DynamicValue;
ariaLabel?: DynamicValue;
clearButtonAriaLabel?: DynamicValue;
@@ -145,6 +146,7 @@ export interface ComboboxPreviewProps {
onChangeDatabaseEvent: {} | null;
onEnterEvent: {} | null;
onLeaveEvent: {} | null;
+ onChangeFilterInputEvent: {} | null;
ariaRequired: string;
ariaLabel: string;
clearButtonAriaLabel: string;