Skip to content

feat: added EsAutocomplete#1777

Draft
ericdouglaspratt wants to merge 24 commits into
mainfrom
ceo-683-autocomplete
Draft

feat: added EsAutocomplete#1777
ericdouglaspratt wants to merge 24 commits into
mainfrom
ceo-683-autocomplete

Conversation

@ericdouglaspratt

@ericdouglaspratt ericdouglaspratt commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

🔗 Linked issue

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

  • Added EsAutocomplete built on Reka UI's Autocomplete and Dialog primitive components
  • Note: functionality and UX are built, but it needs:
    - a thorough, line-by-line code review
    - probably a complete rewrite of the docs page to be more concise and with clearer, more consistent examples
    - accessibility testing
    - mobile device and browser testing
    - a POC integration with the downstream functionality to ensure it can work with that
    - removal of the docs/plans markdown file once complete

🥼 Testing

  • Tested locally

🧐 Feedback Requested / Focus Areas

  • Overall

📝 Checklist

  • I have verified accessibility of any new components by:
    • automated check with the WAVE browser extension
    • navigating by keyboard
    • using with a screen reader (e.g. VoiceOver on Safari)
  • I have updated any applicable documentation.

ericdouglaspratt and others added 24 commits July 2, 2026 14:43
Reka auto-highlights the first item whenever results arrive from an empty
list, so Enter right after typing selected that item instead of submitting
the typed query. Track whether the highlight was user-initiated (arrow keys
or pointer movement) and handle Enter in the capture phase on the anchor:
a non-user highlight now submits the raw query and stops the event before
Reka's own handler can select.

Review finding 1 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ition

The submit path now calls preventDefault so a surrounding <form> does not
natively submit alongside the component's submit event (Reka only prevents
Enter's default when selecting a highlighted item), and ignores the Enter
that commits an IME composition, which previously fired submit with
half-composed text.

Review finding 2 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ResizeObserver watches the list container, whose height the trim itself
controls, so when available height grew (window resized taller) a trimmed
container never resized and the observer stayed silent — the list was stuck
at its old item count until the suggestions changed. A window resize now
re-measures directly, deferred one frame so the popper's own resize handling
updates the available-height constraint first (which also coalesces resize
bursts to one measure per frame).

Review finding 3 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
noResults was keyed to the previous query's response, so after an empty
result, lengthening the query displayed "No results found" for the new
query while its search was still in flight — contradicting the documented
behavior. Reset noResults as soon as the query changes above minChars.

Review finding 4 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The blanket mousedown.prevent on the panel blocked focus for any interactive
element a consumer renders via the item slot; it now only prevents default
for non-interactive targets, and focusout additionally ignores focus moving
into the panel so such elements stay usable. focusout also ignores the
window itself losing focus (alt-tab, devtools) — the input is still the
active element, so the panel no longer disappears mid-interaction.

Review finding 5 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… machinery

The measuring/pending/settledHeight flags and the do-while re-entrancy loop
existed only because the ResizeObserver watched the same element whose height
the trim itself mutates. The height limit is now read from the container's
resolved max-height (desktop popover) or explicit height (mobile takeover
list) — both stable while items render — so measuring is idempotent and the
observer plus all three guards are deleted. Triggers are now explicit:
suggestion changes, window resizes, and the mobile shell re-measuring after
it sets the list height from the visual viewport.

Review finding 7 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The clear button was Reka's AutocompleteCancel with a tabindex=0 attribute
that only overrode the hardcoded tabindex=-1 via undocumented Vue attr-merge
order — an in-range reka-ui bump could silently make it keyboard-unreachable.
It is now a plain button we fully own (clear + refocus), naturally tabbable.
The Enter capture handler now also ignores Enter originating from the clear
button so it acts as a click rather than a submit.

The mobile shell resolved the panel element with a computed over the
non-reactive $el — the exact pattern the desktop shell's comment calls
broken. Both shells now share useAutocompleteContentEl, which re-resolves
the element when the open state that mounts the panel changes.

Review finding 9 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shells duplicated ~150 lines each. Consolidated:

- panelMessage is computed once in the parent (which owns model, minChars,
  noResults, and the message texts) and passed down as a single prop,
  replacing four forwarded props per shell
- the shells' withDefaults blocks were dead code — the parent always binds
  every prop, so shell defaults could never apply and could silently drift;
  shells now declare plain typed props
- Enter/select/clear handling and the user-highlight tracking (byte-identical
  including comments) moved to a shared useAutocompleteShell composable
- the label block and the clear button (with its styles) are now shared
  es-autocomplete-label / es-autocomplete-clear-button components
- the suggestions ref wrapper computeds became toRef(props, 'suggestions')

The shells now carry only their genuine differences: popover chrome + focus
open/close model vs dialog takeover chrome + visual-viewport handling, and
their MAX_VISIBLE caps. Behavior-preserving.

Review finding 6 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…able guards

The debounce timer was cleared at three separate call sites that each had to
remember the same bookkeeping; scheduleComplete/cancelPendingComplete now
centralize it, so a future terminal path can't forget the cancel and
re-introduce the late-response-reopens-panel bug.

Removed the 'model.value ?? empty-string' guards that spread to eight sites:
defineModel<string>({ default: '' }) makes undefined unrepresentable through
the typed API, so the branches were unreachable and implied a contract that
does not exist.

Deliberately NOT adopted from the review: replacing the timer with @vueuse
watchDebounced (it would be this source-shipped package's first direct
@vueuse/core import, and the dependency is a devDependency — a runtime
resolution risk for consumers not worth taking here), and DS-wide
consolidation of the overlay/label markup with es-menu-bar/es-form-input
(requires an es-ds-styles change; the autocomplete-internal copies were
already deduplicated in the previous commit).

Review finding 8 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- watch suggestions with depth 1 so apps that mutate the array in place
  (push/splice) still update noResults and re-trim the list
- guard against toLowerCase changing string length (e.g. İ), which would
  misalign highlight segment offsets; fall back to case-sensitive matching
- share one EMPTY_SUGGESTIONS constant so the below-minChars computed is
  referentially stable and doesn't re-fire downstream watchers per keystroke
- coalesce visualViewport resize handling to one layout read/write per frame
  during the mobile keyboard animation
- docs: add the v-model row to the props table (per the text-input page
  precedent)
- import splitAutocompleteText explicitly in the component (matching the
  explicit-utils-import precedent set by es-menu-bar)
- gitignore .playwright-mcp/ browser-session artifacts

Review finding 10 of 10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant