diff --git a/docs/src/assets/styles.css b/docs/src/assets/styles.css index 3c47a12..26547a1 100644 --- a/docs/src/assets/styles.css +++ b/docs/src/assets/styles.css @@ -3279,7 +3279,7 @@ margin-block: calc(var(--spacing) * 1); border-color: var(--color-border); } - &:not(:has([data-value]:not([aria-hidden='true'])))::before { + &:not(:has([role='menuitem']:not([aria-hidden='true'])))::before { margin: calc(var(--spacing) * -1); display: flex; align-items: center; @@ -3292,11 +3292,11 @@ font-size: var(--text-sm); line-height: var(--tw-leading, var(--text-sm--line-height)); } - &[data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before { + &[data-empty]:not(:has([role='menuitem']:not([aria-hidden='true'])))::before { --tw-content: attr(data-empty); content: var(--tw-content); } - &:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before { + &:not([data-empty]):not(:has([role='menuitem']:not([aria-hidden='true'])))::before { --tw-content: 'No results found'; content: var(--tw-content); } diff --git a/docs/src/components/combobox.njk b/docs/src/components/combobox.njk index 12f5c16..49e2f32 100644 --- a/docs/src/components/combobox.njk +++ b/docs/src/components/combobox.njk @@ -118,7 +118,14 @@ toc:
<div role="option" data-value="{ VALUE }">
-
Option that can be selected.Should have a unique id if you use the aria-activedescendant attribute on the trigger.
+
Option that can be selected. Should have a unique id if you use the aria-activedescendant attribute on the trigger. Optional attributes: + +
<hr role="separator"> Optional
Separator between groups/options.
<div role="group"> Optional
diff --git a/docs/src/components/command.njk b/docs/src/components/command.njk index 41c0d9e..ec348c0 100644 --- a/docs/src/components/command.njk +++ b/docs/src/components/command.njk @@ -33,15 +33,15 @@ toc: {% macro command_items(suggestions_id="suggestions", settings_id="settings") %}
Suggestions -
+
{% lucide "calendar" %} Calendar
-
+
{% lucide "smile" %} Search Emoji
-
+
{% lucide "calculator" %} Calculator
@@ -49,17 +49,17 @@ toc:
Settings -
+
{% lucide "user" %} Profile ⌘P
-
+
{% lucide "credit-card" %} Billing ⌘B
-
+
{% lucide "settings" %} Settings ⌘S @@ -158,8 +158,9 @@ toc:
<div role="menuitem">
Command item that triggers an action when clicked. In a command-dialog, clicking a menuitem automatically closes the dialog unless the data-keep-command-open attribute is present. Optional attributes:
    -
  • data-label: custom label for filtering (if different from text content)
  • -
  • data-keywords: additional keywords for enhanced filtering
  • +
  • data-filter: custom text used for filtering (if different from displayed content, e.g., data-filter="calendar event")
  • +
  • data-keywords: additional keywords for enhanced filtering (e.g., data-keywords="calendar event date")
  • +
  • data-force: forces the item to always display, regardless of filter (e.g., data-force)
  • aria-disabled="true": marks item as disabled (excluded from keyboard navigation and filtering)
@@ -209,11 +210,11 @@ toc:
{% set raw_code %}{% raw %}{% call command() %} -
+
... Calendar
-
+
... Settings
diff --git a/docs/src/components/select.njk b/docs/src/components/select.njk index 8f84d1a..0fc789f 100644 --- a/docs/src/components/select.njk +++ b/docs/src/components/select.njk @@ -58,8 +58,8 @@ toc: type: "group", label: "Fruits", items: [ - { type: "item", value: "apple", label: "Apple" }, - { type: "item", value: "banana", label: "Banana" }, + { type: "item", value: "apple", label: "Apple", attrs: {"data-force": "true"} }, + { type: "item", value: "banana", label: "Banana", attrs: {"data-keywords": "bread"} }, { type: "item", value: "blueberry", label: "Blueberry" }, { type: "item", value: "pineapple", label: "Grapes" }, { type: "item", value: "pineapple", label: "Pineapple" } diff --git a/src/css/basecoat.css b/src/css/basecoat.css index 1eb314e..32bb087 100644 --- a/src/css/basecoat.css +++ b/src/css/basecoat.css @@ -572,13 +572,13 @@ [role='separator'] { @apply border-border -mx-1 my-1; } - &:not(:has([data-value]:not([aria-hidden='true'])))::before { + &:not(:has([role='menuitem']:not([aria-hidden='true'])))::before { @apply flex items-center justify-center py-6 px-3 text-sm truncate -m-1; } - &[data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before { + &[data-empty]:not(:has([role='menuitem']:not([aria-hidden='true'])))::before { @apply content-[attr(data-empty)]; } - &:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before { + &:not([data-empty]):not(:has([role='menuitem']:not([aria-hidden='true'])))::before { @apply content-['No_results_found']; } } diff --git a/src/js/command.js b/src/js/command.js index c4970ea..c5fad8d 100644 --- a/src/js/command.js +++ b/src/js/command.js @@ -46,7 +46,15 @@ visibleMenuItems = []; allMenuItems.forEach(item => { - const itemText = (item.dataset.label || item.textContent).trim().toLowerCase(); + if (item.hasAttribute('data-force')) { + item.setAttribute('aria-hidden', 'false'); + if (menuItems.includes(item)) { + visibleMenuItems.push(item); + } + return; + } + + const itemText = (item.dataset.filter || item.textContent).trim().toLowerCase(); const keywords = (item.dataset.keywords || '').toLowerCase(); const matches = itemText.includes(searchTerm) || keywords.includes(searchTerm); item.setAttribute('aria-hidden', String(!matches)); diff --git a/src/js/select.js b/src/js/select.js index 352dcb8..48118c7 100644 --- a/src/js/select.js +++ b/src/js/select.js @@ -48,7 +48,7 @@ const updateValue = (option, triggerEvent = true) => { if (option) { - selectedLabel.innerHTML = option.dataset.label || option.innerHTML; + selectedLabel.innerHTML = option.innerHTML; input.value = option.dataset.value; listbox.querySelector('[role="option"][aria-selected="true"]')?.removeAttribute('aria-selected'); option.setAttribute('aria-selected', 'true'); @@ -112,8 +112,17 @@ visibleOptions = []; allOptions.forEach(option => { - const optionText = (option.dataset.label || option.textContent).trim().toLowerCase(); - const matches = optionText.includes(searchTerm); + if (option.hasAttribute('data-force')) { + option.setAttribute('aria-hidden', 'false'); + if (options.includes(option)) { + visibleOptions.push(option); + } + return; + } + + const optionText = (option.dataset.filter || option.textContent).trim().toLowerCase(); + const keywords = (option.dataset.keywords || '').toLowerCase(); + const matches = optionText.includes(searchTerm) || keywords.includes(searchTerm); option.setAttribute('aria-hidden', String(!matches)); if (matches && options.includes(option)) { visibleOptions.push(option);