diff --git a/packages/ui/src/combobox.js b/packages/ui/src/combobox.js
index b0ac15b72..50739fcc8 100644
--- a/packages/ui/src/combobox.js
+++ b/packages/ui/src/combobox.js
@@ -246,7 +246,15 @@ function handleRoot(el, Alpine) {
if (typeof by === 'string') {
let property = by
- by = (a, b) => a[property] === b[property]
+ by = (a, b) => {
+ // Handle null values
+ if ((! a || typeof a !== 'object') || (! b || typeof b !== 'object')) {
+ return Alpine.raw(a) === Alpine.raw(b)
+ }
+
+
+ return a[property] === b[property];
+ }
}
return by(a, b)
@@ -432,9 +440,9 @@ function handleOption(el, Alpine) {
// Only the active element should have aria-selected="true"...
'x-effect'() {
- this.$comboboxOption.isActive
+ this.$comboboxOption.isSelected
? el.setAttribute('aria-selected', true)
- : el.removeAttribute('aria-selected')
+ : el.setAttribute('aria-selected', false)
},
':aria-disabled'() { return this.$comboboxOption.isDisabled },
diff --git a/packages/ui/src/list-context.js b/packages/ui/src/list-context.js
index da95148bc..bd93a2748 100644
--- a/packages/ui/src/list-context.js
+++ b/packages/ui/src/list-context.js
@@ -292,6 +292,8 @@ export function generateContext(Alpine, multiple, orientation, activateSelectedO
break;
case 'Home':
case 'PageUp':
+ if (e.key == 'Home' && e.shiftKey) return;
+
e.preventDefault(); e.stopPropagation()
setIsTyping(false)
this.reorderKeys(); hasActive = this.hasActive()
@@ -300,6 +302,8 @@ export function generateContext(Alpine, multiple, orientation, activateSelectedO
case 'End':
case 'PageDown':
+ if (e.key == 'End' && e.shiftKey) return;
+
e.preventDefault(); e.stopPropagation()
setIsTyping(false)
this.reorderKeys(); hasActive = this.hasActive()
diff --git a/tests/cypress/integration/plugins/ui/combobox.spec.js b/tests/cypress/integration/plugins/ui/combobox.spec.js
index a529fc4f0..b3225dfba 100644
--- a/tests/cypress/integration/plugins/ui/combobox.spec.js
+++ b/tests/cypress/integration/plugins/ui/combobox.spec.js
@@ -708,6 +708,155 @@ test('"multiple" and "name" props together',
},
);
+test('"by" prop with string value',
+ [html`
+
+
+
+
+
+
+
+
+ `],
+ ({ get }) => {
+ get('ul').should(notBeVisible())
+ get('button').click()
+ get('ul').should(beVisible())
+ get('button').click()
+ get('ul').should(notBeVisible())
+ get('button').click()
+ get('[option="2"]').click()
+ get('ul').should(notBeVisible())
+ get('input').should(haveValue('2'))
+ get('button').should(haveText('2'))
+ get('button').click()
+ get('ul').should(contain('Wade Cooper'))
+ .should(contain('Arlene Mccoy'))
+ .should(contain('Devon Webb'))
+ get('[option="3"]').click()
+ get('ul').should(notBeVisible())
+ get('input').should(haveValue('3'))
+ get('button').should(haveText('3'))
+ get('button').click()
+ get('ul').should(contain('Wade Cooper'))
+ .should(contain('Arlene Mccoy'))
+ .should(contain('Devon Webb'))
+ get('[option="1"]').click()
+ get('ul').should(notBeVisible())
+ get('input').should(haveValue('1'))
+ get('button').should(haveText('1'))
+ },
+);
+
+test('"by" prop with string value and "nullable"',
+ [html`
+
+
+
+
+
+
+
+
+ `],
+ ({ get }) => {
+ get('ul').should(notBeVisible())
+ get('button').click()
+ get('ul').should(beVisible())
+ get('button').click()
+ get('ul').should(notBeVisible())
+ get('button').click()
+ get('[option="2"]').click()
+ get('ul').should(notBeVisible())
+ get('input').should(haveValue('Arlene Mccoy'))
+ get('button').should(haveText('Arlene Mccoy'))
+ get('button').click()
+ get('ul').should(contain('Wade Cooper'))
+ .should(contain('Arlene Mccoy'))
+ .should(contain('Devon Webb'))
+ get('[option="3"]').click()
+ get('ul').should(notBeVisible())
+ get('input').should(haveValue('Devon Webb'))
+ get('button').should(haveText('Devon Webb'))
+ get('button').click()
+ get('ul').should(contain('Wade Cooper'))
+ .should(contain('Arlene Mccoy'))
+ .should(contain('Devon Webb'))
+ get('[option="1"]').click()
+ get('ul').should(notBeVisible())
+ get('input').should(haveValue('Wade Cooper'))
+ get('button').should(haveText('Wade Cooper'))
+ },
+);
+
+
test('keyboard controls',
[html`
@@ -1287,19 +1441,120 @@ test('active element logic when opening a combobox',
get('button').click()
// First option is selected on opening if no preselection
get('ul').should(beVisible())
- get('[option="1"]').should(haveAttribute('aria-selected', 'true'))
+ get('[option="1"]').should(haveAttribute('aria-selected', 'false'))
+ get('[option="1"]').should(haveClasses(['active']))
// First match is selected while typing
- get('[option="4"]').should(notHaveAttribute('aria-selected'))
+ get('[option="4"]').should(haveAttribute('aria-selected', 'false'))
+ get('[option="4"]').should(notHaveClasses(['active']))
get('input').type('T')
get('input').trigger('change')
- get('[option="4"]').should(haveAttribute('aria-selected', 'true'))
+ get('[option="4"]').should(haveAttribute('aria-selected', 'false'))
+ get('[option="4"]').should(haveClasses(['active']))
// Reset state and select option 3
get('button').click()
get('button').click()
get('[option="3"]').click()
// Previous selection is selected
get('button').click()
- get('[option="4"]').should(notHaveAttribute('aria-selected'))
+ get('[option="4"]').should(haveAttribute('aria-selected', 'false'))
get('[option="3"]').should(haveAttribute('aria-selected', 'true'))
}
)
+
+test('can remove an option without other options getting removed',
+ [html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No frameworks match your query.
+
+
+
+
+ `],
+ ({ get }) => {
+ get('input').type('a').trigger('input')
+ cy.wait(100)
+ get('[option="1"]').click()
+ get('[option="2"]').click()
+ get('[option="3"]').click()
+ get('[remove-option="3"]').click()
+ get('[option="1"]').should(haveAttribute('aria-selected', 'true'))
+ get('[option="2"]').should(haveAttribute('aria-selected', 'true'))
+ get('[option="3"]').should(haveAttribute('aria-selected', 'false'))
+ get('input').type('a').trigger('input')
+ get('[check="1"]').should(beVisible())
+ get('[check="2"]').should(beVisible())
+ get('[check="3"]').should(notBeVisible())
+ },
+);