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..4d19b55e67 100644
--- a/packages/pluggableWidgets/combobox-web/package.json
+++ b/packages/pluggableWidgets/combobox-web/package.json
@@ -1,7 +1,7 @@
{
"name": "@mendix/combobox-web",
"widgetName": "Combobox",
- "version": "2.4.3",
+ "version": "2.5.0",
"description": "Configurable Combo box widget with suggestions and autocomplete.",
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
"license": "Apache-2.0",
@@ -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.editorConfig.ts b/packages/pluggableWidgets/combobox-web/src/Combobox.editorConfig.ts
index d232b04d11..86108e9a14 100644
--- a/packages/pluggableWidgets/combobox-web/src/Combobox.editorConfig.ts
+++ b/packages/pluggableWidgets/combobox-web/src/Combobox.editorConfig.ts
@@ -178,6 +178,10 @@ export function getProperties(
hidePropertiesIn(defaultProperties, values, ["loadingType"]);
}
+ if (values.onChangeFilterInputEvent === null) {
+ hidePropertiesIn(defaultProperties, values, ["filterInputDebounceInterval"]);
+ }
+
return defaultProperties;
}
diff --git a/packages/pluggableWidgets/combobox-web/src/Combobox.xml b/packages/pluggableWidgets/combobox-web/src/Combobox.xml
index 5f87880ab5..c1994358df 100644
--- a/packages/pluggableWidgets/combobox-web/src/Combobox.xml
+++ b/packages/pluggableWidgets/combobox-web/src/Combobox.xml
@@ -314,21 +314,33 @@
- On change action
+ On change
- On change action
+ On change
- On enter action
+ On enter
- On leave action
+ On leave
+
+
+ On filter input change
+
+
+
+
+
+
+ Debounce interval
+ The debounce interval for each filter input change event triggered in milliseconds.
+
diff --git a/packages/pluggableWidgets/combobox-web/src/__tests__/MultiSelection.spec.tsx b/packages/pluggableWidgets/combobox-web/src/__tests__/MultiSelection.spec.tsx
index be4ee06860..694265e13e 100644
--- a/packages/pluggableWidgets/combobox-web/src/__tests__/MultiSelection.spec.tsx
+++ b/packages/pluggableWidgets/combobox-web/src/__tests__/MultiSelection.spec.tsx
@@ -76,7 +76,8 @@ describe("Combo box (Association)", () => {
],
selectedItemsSorting: "none",
customEditability: "default",
- customEditabilityExpression: dynamic(false)
+ customEditabilityExpression: dynamic(false),
+ filterInputDebounceInterval: 200
};
if (defaultProps.optionsSourceAssociationCaptionType === "expression") {
defaultProps.optionsSourceAssociationCaptionExpression!.get = i => dynamic(`${i.id}`);
diff --git a/packages/pluggableWidgets/combobox-web/src/__tests__/SingleSelection.spec.tsx b/packages/pluggableWidgets/combobox-web/src/__tests__/SingleSelection.spec.tsx
index 9bc51c9330..a61740d899 100644
--- a/packages/pluggableWidgets/combobox-web/src/__tests__/SingleSelection.spec.tsx
+++ b/packages/pluggableWidgets/combobox-web/src/__tests__/SingleSelection.spec.tsx
@@ -79,7 +79,8 @@ describe("Combo box (Association)", () => {
],
selectedItemsSorting: "none",
customEditability: "default",
- customEditabilityExpression: dynamic(false)
+ customEditabilityExpression: dynamic(false),
+ filterInputDebounceInterval: 200
};
if (defaultProps.optionsSourceAssociationCaptionType === "expression") {
defaultProps.optionsSourceAssociationCaptionExpression!.get = i => dynamic(`${i.id}`);
diff --git a/packages/pluggableWidgets/combobox-web/src/__tests__/StaticSelection.spec.tsx b/packages/pluggableWidgets/combobox-web/src/__tests__/StaticSelection.spec.tsx
index 8d71b176bb..698dcafe57 100644
--- a/packages/pluggableWidgets/combobox-web/src/__tests__/StaticSelection.spec.tsx
+++ b/packages/pluggableWidgets/combobox-web/src/__tests__/StaticSelection.spec.tsx
@@ -77,7 +77,8 @@ describe("Combo box (Static values)", () => {
],
selectedItemsSorting: "none",
customEditability: "default",
- customEditabilityExpression: dynamic(false)
+ customEditabilityExpression: dynamic(false),
+ filterInputDebounceInterval: 200
};
if (defaultProps.optionsSourceAssociationCaptionType === "expression") {
defaultProps.optionsSourceAssociationCaptionExpression!.get = i => dynamic(`${i.id}`);
diff --git a/packages/pluggableWidgets/combobox-web/src/helpers/types.ts b/packages/pluggableWidgets/combobox-web/src/helpers/types.ts
index edf5013642..1fb5795f7c 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..a1ec0b1870 100644
--- a/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftMultiSelectProps.ts
+++ b/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftMultiSelectProps.ts
@@ -2,12 +2,13 @@ import {
UseComboboxProps,
UseComboboxReturnValue,
UseComboboxState,
+ UseComboboxStateChange,
UseComboboxStateChangeOptions,
UseMultipleSelectionReturnValue,
useCombobox,
useMultipleSelection
} from "downshift";
-import { useMemo, useCallback } from "react";
+import { useCallback, useMemo } from "react";
import { A11yStatusMessage, MultiSelector } from "../helpers/types";
export type UseDownshiftMultiSelectPropsReturnValue = UseMultipleSelectionReturnValue &
@@ -148,8 +149,11 @@ function useComboboxProps(
selectedItem: null,
inputId: options?.inputId,
labelId: options?.labelId,
- onInputValueChange({ inputValue }) {
+ onInputValueChange({ inputValue, type }: UseComboboxStateChange) {
selector.options.setSearchTerm(inputValue!);
+ if (selector.onFilterInputChange && type === useCombobox.stateChangeTypes.InputChange) {
+ 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..707e343d03 100644
--- a/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftSingleSelectProps.ts
+++ b/packages/pluggableWidgets/combobox-web/src/hooks/useDownshiftSingleSelectProps.ts
@@ -2,12 +2,12 @@ import {
UseComboboxProps,
UseComboboxReturnValue,
UseComboboxState,
- UseComboboxStateChange,
UseComboboxStateChangeOptions,
+ UseComboboxStateChange,
useCombobox
} from "downshift";
-import { useMemo, useCallback } from "react";
+import { useCallback, useMemo } from "react";
import { A11yStatusMessage, SingleSelector } from "../helpers/types";
interface Options {
@@ -29,8 +29,11 @@ export function useDownshiftSingleSelectProps(
onSelectedItemChange({ selectedItem }: UseComboboxStateChange) {
selector.setValue(selectedItem ?? null);
},
- onInputValueChange({ inputValue }) {
+ onInputValueChange({ inputValue, type }: UseComboboxStateChange) {
selector.options.setSearchTerm(inputValue!);
+ if (selector.onFilterInputChange && type === useCombobox.stateChangeTypes.InputChange) {
+ 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..95713bd07b 100644
--- a/packages/pluggableWidgets/combobox-web/src/hooks/useGetSelector.ts
+++ b/packages/pluggableWidgets/combobox-web/src/hooks/useGetSelector.ts
@@ -1,15 +1,43 @@
-import { useRef, useState } from "react";
+import { debounce } from "@mendix/widget-plugin-platform/utils/debounce";
+import { useMemo, useRef, useState } from "react";
import { ComboboxContainerProps } from "../../typings/ComboboxProps";
import { getSelector } from "../helpers/getSelector";
import { Selector } from "../helpers/types";
+function onInputValueChange(
+ onChangeFilterInputEvent: ComboboxContainerProps["onChangeFilterInputEvent"],
+ filterValue?: string
+): void {
+ if (!onChangeFilterInputEvent) {
+ return;
+ }
+ if (onChangeFilterInputEvent.canExecute && !onChangeFilterInputEvent.isExecuting) {
+ onChangeFilterInputEvent.execute({
+ filterInput: filterValue
+ });
+ }
+}
+
export function useGetSelector(props: ComboboxContainerProps): Selector {
const selectorRef = useRef(undefined);
const [, setInput] = useState({});
+ const [onFilterChangeDebounce] = useMemo(
+ () =>
+ debounce((filterValue?: string) => {
+ onInputValueChange(props.onChangeFilterInputEvent, filterValue);
+ }, props.filterInputDebounceInterval ?? 200),
+ [props.onChangeFilterInputEvent, props.filterInputDebounceInterval]
+ );
+
if (!selectorRef.current) {
selectorRef.current = getSelector(props);
selectorRef.current.options.onAfterSearchTermChange(() => setInput({}));
+ } else {
+ if (!selectorRef.current.onFilterInputChange) {
+ selectorRef.current.onFilterInputChange = onFilterChangeDebounce;
+ }
}
selectorRef.current.updateProps(props);
+
return selectorRef.current;
}
diff --git a/packages/pluggableWidgets/combobox-web/src/package.xml b/packages/pluggableWidgets/combobox-web/src/package.xml
index 5fcc7377bf..7607566d28 100644
--- a/packages/pluggableWidgets/combobox-web/src/package.xml
+++ b/packages/pluggableWidgets/combobox-web/src/package.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/packages/pluggableWidgets/combobox-web/typings/ComboboxProps.d.ts b/packages/pluggableWidgets/combobox-web/typings/ComboboxProps.d.ts
index d23d3a96ac..aec3e503fb 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,8 @@ export interface ComboboxContainerProps {
onChangeEvent?: ActionValue;
onEnterEvent?: ActionValue;
onLeaveEvent?: ActionValue;
+ onChangeFilterInputEvent?: ActionValue<{ filterInput: Option }>;
+ filterInputDebounceInterval: number;
ariaRequired: DynamicValue;
ariaLabel?: DynamicValue;
clearButtonAriaLabel?: DynamicValue;
@@ -145,6 +147,8 @@ export interface ComboboxPreviewProps {
onChangeDatabaseEvent: {} | null;
onEnterEvent: {} | null;
onLeaveEvent: {} | null;
+ onChangeFilterInputEvent: {} | null;
+ filterInputDebounceInterval: number | null;
ariaRequired: string;
ariaLabel: string;
clearButtonAriaLabel: string;