Skip to content

[WC-3026] Combobox: Enable onFilterInputChange action with ActionVariables #1782

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/combobox-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/pluggableWidgets/combobox-web/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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
Expand Down
21 changes: 17 additions & 4 deletions packages/pluggableWidgets/combobox-web/src/Combobox.xml
Original file line number Diff line number Diff line change
Expand Up @@ -314,21 +314,34 @@

<propertyGroup caption="Events">
<property key="onChangeEvent" type="action" required="false">
<caption>On change action</caption>
<caption>On change</caption>
<description />
</property>
<property key="onChangeDatabaseEvent" type="action" required="false">
<caption>On change action</caption>
<caption>On change</caption>
<description />
</property>
<property key="onEnterEvent" type="action" required="false">
<caption>On enter action</caption>
<caption>On enter</caption>
<description />
</property>
<property key="onLeaveEvent" type="action" required="false">
<caption>On leave action</caption>
<caption>On leave</caption>
<description />
</property>

<property key="onChangeFilterInputEvent" type="action" required="false">
<caption>On filter input change</caption>
<description />
<actionVariables>
<actionVariable key="filterInput" caption="Filter Input" type="String" />
</actionVariables>
</property>

<property key="debounceInterval" type="integer" required="true" defaultValue="200">
<caption>Debounce interval</caption>
<description>The debounce interval in milliseconds.</description>
</property>
</propertyGroup>
<propertyGroup caption="Accessibility">
<propertyGroup caption="Accessibility">
Expand Down
2 changes: 2 additions & 0 deletions packages/pluggableWidgets/combobox-web/src/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ interface SelectorBase<T, V> {

onEnterEvent?: () => void;
onLeaveEvent?: () => void;
onFilterInputChange?: (filterValue?: string) => void;
}

export interface SingleSelector extends SelectorBase<"single", string> {}
Expand All @@ -101,6 +102,7 @@ export interface SelectionBaseProps<Selector> {
tabIndex: number;
ariaRequired: DynamicValue<boolean>;
ariaLabel?: string;
onFilterInputChange?: (filterValue: string) => void;
a11yConfig: {
ariaLabels: {
clearSelection: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useCombobox,
useMultipleSelection
} from "downshift";
import { useMemo, useCallback } from "react";
import { useCallback, useMemo } from "react";
import { A11yStatusMessage, MultiSelector } from "../helpers/types";

export type UseDownshiftMultiSelectPropsReturnValue = UseMultipleSelectionReturnValue<string> &
Expand Down Expand Up @@ -150,6 +150,9 @@ function useComboboxProps(
labelId: options?.labelId,
onInputValueChange({ inputValue }) {
selector.options.setSearchTerm(inputValue!);
if (selector.onFilterInputChange) {
selector.onFilterInputChange(inputValue);
}
},
getA11yStatusMessage(options) {
let message =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useCombobox
} from "downshift";

import { useMemo, useCallback } from "react";
import { useCallback, useMemo, useRef, useEffect } from "react";
import { A11yStatusMessage, SingleSelector } from "../helpers/types";

interface Options {
Expand All @@ -21,6 +21,11 @@ export function useDownshiftSingleSelectProps(
a11yStatusMessage: A11yStatusMessage
): UseComboboxReturnValue<string> {
const { inputId, labelId } = options;
const isInitializing = useRef(true);

useEffect(() => {
isInitializing.current = false;
}, []);

const downshiftProps: UseComboboxProps<string> = useMemo(() => {
return {
Expand All @@ -29,8 +34,15 @@ export function useDownshiftSingleSelectProps(
onSelectedItemChange({ selectedItem }: UseComboboxStateChange<string>) {
selector.setValue(selectedItem ?? null);
},
onInputValueChange({ inputValue }) {
onInputValueChange({ inputValue, type }) {
selector.options.setSearchTerm(inputValue!);
if (
!isInitializing.current &&
type === useCombobox.stateChangeTypes.InputChange &&
selector.onFilterInputChange
) {
selector.onFilterInputChange(inputValue!);
}
},
getA11yStatusMessage(options) {
const selectedItem = selector.caption.get(selector.currentId);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Selector | undefined>(undefined);
const [, setInput] = useState({});
const [onFilterChangeDebounce] = useMemo(
() =>
debounce((filterValue?: string) => {
onInputValueChange(props.onChangeFilterInputEvent, filterValue);
}, props.debounceInterval ?? 200),
[props.onChangeFilterInputEvent, props.debounceInterval]
);

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;
}
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/combobox-web/src/package.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="Combobox" version="2.4.3" xmlns="http://www.mendix.com/clientModule/1.0/">
<clientModule name="Combobox" version="2.5.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="Combobox.xml" />
</widgetFiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -89,6 +89,8 @@ export interface ComboboxContainerProps {
onChangeEvent?: ActionValue;
onEnterEvent?: ActionValue;
onLeaveEvent?: ActionValue;
onChangeFilterInputEvent?: ActionValue<{ filterInput: Option<string> }>;
debounceInterval?: number;
ariaRequired: DynamicValue<boolean>;
ariaLabel?: DynamicValue<string>;
clearButtonAriaLabel?: DynamicValue<string>;
Expand Down Expand Up @@ -145,6 +147,8 @@ export interface ComboboxPreviewProps {
onChangeDatabaseEvent: {} | null;
onEnterEvent: {} | null;
onLeaveEvent: {} | null;
onChangeFilterInputEvent: {} | null;
debounceInterval: number | null;
ariaRequired: string;
ariaLabel: string;
clearButtonAriaLabel: string;
Expand Down
Loading