diff --git a/packages/calcite-components/src/components/combobox/combobox.e2e.ts b/packages/calcite-components/src/components/combobox/combobox.e2e.ts
index b20ae4a6fad..05ddf88d38e 100644
--- a/packages/calcite-components/src/components/combobox/combobox.e2e.ts
+++ b/packages/calcite-components/src/components/combobox/combobox.e2e.ts
@@ -2219,12 +2219,15 @@ describe("calcite-combobox", () => {
it("should allow enter unknown tag when tabbing away", async () => {
const page = await newE2EPage();
await page.setContent(html`
-
-
-
-
-
-
+
`);
const chip = await page.find("calcite-combobox >>> calcite-chip");
const eventSpy = await page.spyOnEvent("calciteComboboxChange");
@@ -3338,5 +3341,36 @@ describe("calcite-combobox", () => {
},
});
});
+
+ describe("no-matches", () => {
+ themed(
+ async () => {
+ const page = await newE2EPage();
+ await page.setContent(`
+
+
+
+
+ `);
+
+ const combobox = await page.find("calcite-combobox");
+ combobox.setProperty("filterText", "Oak");
+ await page.waitForChanges();
+ await page.waitForTimeout(DEBOUNCE.filter);
+
+ return { tag: "calcite-combobox", page };
+ },
+ {
+ "--calcite-combobox-background-color": {
+ shadowSelector: `.${CSS.noMatches}`,
+ targetProp: "backgroundColor",
+ },
+ "--calcite-combobox-input-text-color": {
+ shadowSelector: `.${CSS.noMatches} >>> mark`,
+ targetProp: "color",
+ },
+ },
+ );
+ });
});
});
diff --git a/packages/calcite-components/src/components/combobox/combobox.scss b/packages/calcite-components/src/components/combobox/combobox.scss
index 6980026544b..a4033dc2d33 100644
--- a/packages/calcite-components/src/components/combobox/combobox.scss
+++ b/packages/calcite-components/src/components/combobox/combobox.scss
@@ -13,6 +13,15 @@
* @prop --calcite-combobox-input-text-color: When `selectionDisplay` is `"single"`, specifies the text color of the component's input.
*/
+// AUTO-GENERATED — do not modify. Changes will be overwritten.
+//
+// Internal CSS custom properties for component use only. Overwriting is not recommended.
+//
+// --calcite-internal-close-size
+// --calcite-internal-combobox-input-margin-block
+// --calcite-internal-combobox-spacing-unit-l
+// --calcite-internal-combobox-spacing-unit-s
+
:host {
@apply relative block;
}
@@ -250,6 +259,20 @@ calcite-chip {
z-index: var(--calcite-z-index-sticky);
}
+.no-matches {
+ padding-block: var(--calcite-internal-combobox-spacing-unit-s);
+ padding-inline: var(--calcite-internal-combobox-spacing-unit-l);
+
+ color: var(--calcite-combobox-input-text-color, var(--calcite-color-text-1));
+ background: var(--calcite-combobox-background-color, var(--calcite-color-foreground-1));
+ cursor: pointer;
+}
+
+.no-matches-placeholder {
+ color: var(--calcite-combobox-icon-color, var(--calcite-color-text-3));
+ cursor: default;
+}
+
@include disabled();
@include x-button(
$background-color: "var(--calcite-close-background-color, var(--calcite-color-foreground-2))",
@@ -259,6 +282,7 @@ calcite-chip {
@include form-validation-message();
@include hidden-form-input();
@include base-component();
+@include text-highlight-item();
::slotted(calcite-combobox-item-group:not(:first-child)) {
padding-block-start: var(--calcite-internal-combobox-spacing-unit-l);
diff --git a/packages/calcite-components/src/components/combobox/combobox.stories.ts b/packages/calcite-components/src/components/combobox/combobox.stories.ts
index b3fd698ce55..2a65bd0dbbf 100644
--- a/packages/calcite-components/src/components/combobox/combobox.stories.ts
+++ b/packages/calcite-components/src/components/combobox/combobox.stories.ts
@@ -1030,6 +1030,35 @@ export const withDescriptionShortLabelAndContentSlots = (): string => html`
`;
+
+export const noMatchesScaledOrAddCustomValue = (): string => html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
withDescriptionShortLabelAndContentSlots.args = {
selectionMode: ["single", "multiple"],
};
diff --git a/packages/calcite-components/src/components/combobox/combobox.tsx b/packages/calcite-components/src/components/combobox/combobox.tsx
index 6b7003688ef..779d150cda8 100644
--- a/packages/calcite-components/src/components/combobox/combobox.tsx
+++ b/packages/calcite-components/src/components/combobox/combobox.tsx
@@ -56,6 +56,7 @@ import { useT9n } from "../../controllers/useT9n";
import type { Chip } from "../chip/chip";
import type { ComboboxItemGroup as HTMLCalciteComboboxItemGroupElement } from "../combobox-item-group/combobox-item-group";
import type { ComboboxItem as HTMLCalciteComboboxItemElement } from "../combobox-item/combobox-item";
+import { highlightText } from "../../utils/text";
import type { Label } from "../label/label";
import { useSetFocus } from "../../controllers/useSetFocus";
import { useCancelable } from "../../controllers/useCancelable";
@@ -142,6 +143,8 @@ export class Combobox
}
});
+ this.noMatchesFound = this.filteredItems.length === 0 && !!this.filterText;
+
this.filterTextMatchPattern =
this.filterText && new RegExp(`(${escapeRegExp(this.filterText)})`, "i");
@@ -150,7 +153,7 @@ export class Combobox
});
if (setOpenToEmptyState) {
- this.open = this.filterText.trim().length > 0 && this.keyboardNavItems.length > 0;
+ this.open = this.filterText.trim().length > 0;
}
if (emit) {
@@ -247,6 +250,29 @@ export class Combobox
private focusSetter = useSetFocus()(this);
+ private get effectiveFilterProps(): string[] {
+ if (!this.filterProps) {
+ return ["description", "label", "metadata", "shortHeading", "textLabel"];
+ }
+
+ return this.filterProps.filter((prop) => prop !== "el");
+ }
+
+ private get showingInlineIcon(): boolean {
+ const { placeholderIcon, selectionMode, selectedItems, open } = this;
+ const selectedItem = selectedItems[0];
+ const selectedIcon = selectedItem?.icon;
+ const singleSelectionMode = isSingleLike(selectionMode);
+
+ return !open && selectedItem
+ ? !!selectedIcon && singleSelectionMode
+ : !!placeholderIcon && (!selectedItem || singleSelectionMode);
+ }
+
+ private customChipAddHandler = (): void => {
+ this.addCustomChip(this.filterText, true);
+ };
+
//#endregion
//#region State Properties
@@ -288,6 +314,8 @@ export class Combobox
return filteredItems;
}
+ @state() noMatchesFound: boolean;
+
//#endregion
//#region Public Properties
@@ -636,29 +664,10 @@ export class Combobox
//#region Private Methods
- private get effectiveFilterProps(): string[] {
- if (!this.filterProps) {
- return ["description", "label", "metadata", "shortHeading", "textLabel"];
- }
-
- return this.filterProps.filter((prop) => prop !== "el");
- }
-
private emitComboboxChange(): void {
this.calciteComboboxChange.emit();
}
- private get showingInlineIcon(): boolean {
- const { placeholderIcon, selectionMode, selectedItems, open } = this;
- const selectedItem = selectedItems[0];
- const selectedIcon = selectedItem?.icon;
- const singleSelectionMode = isSingleLike(selectionMode);
-
- return !open && selectedItem
- ? !!selectedIcon && singleSelectionMode
- : !!placeholderIcon && (!selectedItem || singleSelectionMode);
- }
-
private filterTextChange(value: string): void {
this.updateActiveItemIndex(-1);
this.filterItems(value, true);
@@ -1789,13 +1798,15 @@ export class Combobox
}
private renderFloatingUIContainer(): JsxNode {
- const { setFloatingEl, setContainerEl, open, scale } = this;
+ const { messages, setFloatingEl, setContainerEl, open, scale } = this;
const classes = {
[CSS.listContainer]: true,
[FloatingCSS.animation]: true,
[FloatingCSS.animationActive]: open,
};
+ const label = (this.filterText && messages.add?.replace("{text}", `${this.filterText}`)) ?? "";
+
return (
@@ -1807,16 +1818,35 @@ export class Combobox
class={CSS.selectAll}
id={`${this.guid}-select-all-enabled-interactive`}
indeterminate={this.indeterminate}
- label={this.messages.selectAll}
+ label={messages.selectAll}
ref={this.selectAllComboboxItemReferenceEl}
scale={scale}
selected={this.allSelected}
tabIndex="-1"
- text-label={this.messages.selectAll}
+ text-label={messages.selectAll}
value="select-all"
/>
)}
+ {this.noMatchesFound &&
+ (this.allowCustomValues ? (
+
+ {highlightText({
+ text: label,
+ pattern: new RegExp(`(${escapeRegExp(this.filterText)})`, "i"),
+ })}
+
+ ) : (
+
+ {messages.noMatches}
+
+ ))}
diff --git a/packages/calcite-components/src/components/combobox/resources.ts b/packages/calcite-components/src/components/combobox/resources.ts
index 90414cbdf0f..4135247240a 100644
--- a/packages/calcite-components/src/components/combobox/resources.ts
+++ b/packages/calcite-components/src/components/combobox/resources.ts
@@ -12,6 +12,8 @@ export const CSS = {
label: "label",
labelIcon: "label--icon",
listContainer: "list-container",
+ noMatches: "no-matches",
+ noMatchesPlaceholder: "no-matches-placeholder",
placeholderIcon: "placeholder-icon",
selectAll: "select-all",
selectionDisplayFit: "selection-display--fit",
diff --git a/packages/calcite-components/src/custom-theme/combobox.ts b/packages/calcite-components/src/custom-theme/combobox.ts
index 4a1241dc696..d56ba5c8194 100644
--- a/packages/calcite-components/src/custom-theme/combobox.ts
+++ b/packages/calcite-components/src/custom-theme/combobox.ts
@@ -38,3 +38,10 @@ export const comboboxWithPlaceHolderIcon = html`
`;
+
+export const noMatches = html`
+
+
+
+
+`;