Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ export function getProperties(
hidePropertiesIn(defaultProperties, values, ["loadingType"]);
}

if (values.onChangeFilterInputEvent === null) {
hidePropertiesIn(defaultProperties, values, ["filterInputDebounceInterval"]);
}

return defaultProperties;
}

Expand Down
20 changes: 16 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,33 @@

<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="filterInputDebounceInterval" type="integer" required="true" defaultValue="200">
<caption>Debounce interval</caption>
<description>The debounce interval for each filter input change event triggered in milliseconds.</description>
</property>
</propertyGroup>
<propertyGroup caption="Accessibility">
<propertyGroup caption="Accessibility">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
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 @@ -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<string> &
Expand Down Expand Up @@ -148,8 +149,11 @@ function useComboboxProps(
selectedItem: null,
inputId: options?.inputId,
labelId: options?.labelId,
onInputValueChange({ inputValue }) {
onInputValueChange({ inputValue, type }: UseComboboxStateChange<string>) {
selector.options.setSearchTerm(inputValue!);
if (selector.onFilterInputChange && type === useCombobox.stateChangeTypes.InputChange) {
selector.onFilterInputChange(inputValue);
}
},
getA11yStatusMessage(options) {
let message =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -29,8 +29,11 @@ export function useDownshiftSingleSelectProps(
onSelectedItemChange({ selectedItem }: UseComboboxStateChange<string>) {
selector.setValue(selectedItem ?? null);
},
onInputValueChange({ inputValue }) {
onInputValueChange({ inputValue, type }: UseComboboxStateChange<string>) {
selector.options.setSearchTerm(inputValue!);
if (selector.onFilterInputChange && type === useCombobox.stateChangeTypes.InputChange) {
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.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;
}
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> }>;
filterInputDebounceInterval: 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;
filterInputDebounceInterval: number | null;
ariaRequired: string;
ariaLabel: string;
clearButtonAriaLabel: string;
Expand Down
Loading