Skip to content
Draft
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
21 changes: 17 additions & 4 deletions packages/main/src/ComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,11 @@ enum ValueStateIconMapping {
Information = "information",
}

type SelectionTrigger = "typeahead" | "keyboard" | "click";

type ComboBoxSelectionChangeEventDetail = {
item: ComboBoxItem | null,
trigger: SelectionTrigger,
};

/**
Expand Down Expand Up @@ -506,6 +509,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
_autocomplete = false;
_isKeyNavigation = false;
_selectionPerformed = false;
_selectionTrigger?: SelectionTrigger;
_lastValue: string;
_selectedItemText = "";
_userTypedValue = "";
Expand Down Expand Up @@ -962,6 +966,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}

_handleArrowDown(e: KeyboardEvent, indexOfItem: number) {
this._selectionTrigger = "keyboard";
const isOpen = this.open;

if (this.focused && indexOfItem === -1 && isOpen) {
Expand All @@ -983,6 +988,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}

_handleArrowUp(e: KeyboardEvent, indexOfItem: number) {
this._selectionTrigger = "keyboard";
const isOpen = this.open;

if (indexOfItem === 0) {
Expand All @@ -1002,6 +1008,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}

_handlePageUp(e: KeyboardEvent, indexOfItem: number) {
this._selectionTrigger = "keyboard";
const allItems = this._getItems();
const isProposedIndexValid = indexOfItem - SKIP_ITEMS_SIZE > -1;
indexOfItem = isProposedIndexValid ? indexOfItem - SKIP_ITEMS_SIZE : 0;
Expand All @@ -1011,6 +1018,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}

_handlePageDown(e: KeyboardEvent, indexOfItem: number) {
this._selectionTrigger = "keyboard";
const allItems = this._getItems();
const itemsLength = allItems.length;
const isProposedIndexValid = indexOfItem + SKIP_ITEMS_SIZE < itemsLength;
Expand All @@ -1022,12 +1030,14 @@ class ComboBox extends UI5Element implements IFormInputElement {
}

_handleHome(e: KeyboardEvent) {
this._selectionTrigger = "keyboard";
const shouldMoveForward = isInstanceOfComboBoxItemGroup(this._filteredItems[0]) && !this.open;

this._handleItemNavigation(e, 0, shouldMoveForward);
}

_handleEnd(e: KeyboardEvent) {
this._selectionTrigger = "keyboard";
this._handleItemNavigation(e, this._getItems().length - 1, true /* isForward */);
}

Expand All @@ -1047,6 +1057,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
}

if (isEnter(e)) {
this._selectionPerformed = true;
let focusedItem: IComboBoxItem | undefined;

this._filteredItems.forEach(item => {
Expand Down Expand Up @@ -1351,22 +1362,23 @@ class ComboBox extends UI5Element implements IFormInputElement {
}

const noUserInteraction = !this.focused && !this._isKeyNavigation && !this._selectionPerformed && !this._iconPressed;
// Skip firing "selection-change" event if this is initial rendering or if there has been no user interaction yet
if (this._initialRendering || noUserInteraction) {
return;
}

// Fire selection-change event only when selection actually changes
if (previouslySelectedItem !== itemToBeSelected) {
const trigger = this._selectionTrigger || "typeahead";
this._selectionTrigger = undefined;

if (itemToBeSelected) {
// New item selected
this.fireDecoratorEvent("selection-change", {
item: itemToBeSelected as ComboBoxItem,
trigger,
});
} else if (previouslySelectedItem) {
// Selection cleared - fire event with 'null'
this.fireDecoratorEvent("selection-change", {
item: null,
trigger,
});
}
}
Expand Down Expand Up @@ -1427,6 +1439,7 @@ class ComboBox extends UI5Element implements IFormInputElement {
if (!item.selected) {
this.fireDecoratorEvent("selection-change", {
item,
trigger: "click",
});
}

Expand Down
96 changes: 96 additions & 0 deletions packages/main/test/pages/ComboBox.html
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,102 @@ <h3>ComboBox Composition</h3>
</ui5-combobox>
</div>

<div class="demo-section" style="padding: 20px; background: #f0f8ff; border-radius: 8px;">
<ui5-title>Selection Trigger Flag Demo</ui5-title>
<p style="margin: 10px 0;">The selection-change event includes a <code>trigger</code> property: "typeahead", "enter", "click", or "keyboard"</p>

<ui5-combobox id="trigger-flag-cb" placeholder="Try typing, arrow keys, Enter, or clicking...">
<ui5-cb-item text="Apple" value="apple"></ui5-cb-item>
<ui5-cb-item text="Apricot" value="apricot"></ui5-cb-item>
<ui5-cb-item text="Banana" value="banana"></ui5-cb-item>
<ui5-cb-item text="Blueberry" value="blueberry"></ui5-cb-item>
<ui5-cb-item text="Cherry" value="cherry"></ui5-cb-item>
</ui5-combobox>

<div style="display: flex; gap: 15px; margin-top: 15px;">
<div style="flex: 1; padding: 15px; background: white; border: 2px solid #107e3e; border-radius: 4px; min-height: 100px;">
<div style="font-weight: bold; margin-bottom: 8px;">selection-change events (with trigger):</div>
<div id="trigger-flag-events" style="font-family: monospace; font-size: 13px;">(none yet)</div>
</div>
<div style="flex: 1; padding: 15px; background: white; border: 2px solid #e78c07; border-radius: 4px; min-height: 100px;">
<div style="font-weight: bold; margin-bottom: 8px;">change events:</div>
<div id="trigger-flag-change-events" style="font-family: monospace; font-size: 13px;">(none yet)</div>
</div>
</div>

<div style="margin-top: 10px; padding: 10px; background: #fffbe6; border-radius: 4px; font-size: 13px;">
<strong>Trigger types:</strong>
<span style="margin-left: 10px; padding: 2px 8px; background: #d4edda; border-radius: 3px;">typeahead</span>
<span style="margin-left: 5px; padding: 2px 8px; background: #cce5ff; border-radius: 3px;">keyboard</span>
<span style="margin-left: 5px; padding: 2px 8px; background: #e2d5f1; border-radius: 3px;">click</span>
</div>

<ui5-button id="trigger-flag-clear" style="margin-top: 10px;">Clear Log</ui5-button>

<script>
(function() {
const cb = document.getElementById("trigger-flag-cb");
const selectionEventsEl = document.getElementById("trigger-flag-events");
const changeEventsEl = document.getElementById("trigger-flag-change-events");
const clearBtn = document.getElementById("trigger-flag-clear");
let selectionCount = 0;
let changeCount = 0;

const triggerColors = {
typeahead: "#d4edda",
keyboard: "#cce5ff",
click: "#e2d5f1"
};

cb.addEventListener("ui5-selection-change", function(e) {
selectionCount++;
const item = e.detail.item;
const trigger = e.detail.trigger;
const timestamp = new Date().toLocaleTimeString();
const entry = document.createElement("div");
const bgColor = triggerColors[trigger] || "#f5f5f5";
entry.style.cssText = `padding: 5px 10px; margin: 3px 0; background: ${bgColor}; border-left: 3px solid #107e3e; animation: flashGreen 0.5s;`;
entry.innerHTML = `<strong>#${selectionCount}</strong> [${timestamp}] <span style="background: ${bgColor}; padding: 1px 6px; border-radius: 3px; font-weight: bold;">${trigger}</span> → ${item ? item.text : 'null'}`;

if (selectionEventsEl.textContent === "(none yet)") {
selectionEventsEl.textContent = "";
}
selectionEventsEl.insertBefore(entry, selectionEventsEl.firstChild);
});

cb.addEventListener("ui5-change", function(e) {
changeCount++;
const timestamp = new Date().toLocaleTimeString();
const entry = document.createElement("div");
entry.style.cssText = "padding: 5px 10px; margin: 3px 0; background: #fff4e8; border-left: 3px solid #e78c07; animation: flashOrange2 0.5s;";
entry.innerHTML = `<strong>#${changeCount}</strong> [${timestamp}] value: <strong>"${e.target.value}"</strong>`;

if (changeEventsEl.textContent === "(none yet)") {
changeEventsEl.textContent = "";
}
changeEventsEl.insertBefore(entry, changeEventsEl.firstChild);
});

clearBtn.addEventListener("click", function() {
selectionEventsEl.innerHTML = "(none yet)";
changeEventsEl.innerHTML = "(none yet)";
selectionCount = 0;
changeCount = 0;
});
})();
</script>
<style>
@keyframes flashGreen {
0% { background: #107e3e; color: white; }
100% { background: inherit; color: inherit; }
}
@keyframes flashOrange2 {
0% { background: #e78c07; color: white; }
100% { background: #fff4e8; color: inherit; }
}
</style>
</div>

<script type="module">
document.getElementById("lazy").addEventListener("ui5-input", async event => {
const { value } = event.target;
Expand Down
Loading