feat: Add keyboard navigation to member linking dropdown
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
4b4ec63613
commit
3da0ebcb3f
4 changed files with 255 additions and 12 deletions
|
|
@ -1328,9 +1328,10 @@ Implemented user-member linking functionality in User Edit/Create views with fuz
|
|||
|
||||
**Key Features:**
|
||||
- Autocomplete dropdown with PostgreSQL Trigram fuzzy search
|
||||
- Keyboard navigation (Arrow keys, Enter, Escape)
|
||||
- Link/unlink members to user accounts
|
||||
- Email synchronization between linked entities
|
||||
- WCAG 2.1 AA compliant (ARIA labels)
|
||||
- WCAG 2.1 AA compliant (ARIA labels, keyboard accessibility)
|
||||
- Bilingual UI (English/German)
|
||||
|
||||
### Technical Decisions
|
||||
|
|
@ -1350,7 +1351,45 @@ window.addEventListener("phx:set-input-value", (e) => {
|
|||
```
|
||||
**Rationale:** LiveView DOM patching has race conditions with rapid state changes in autocomplete components. Direct DOM manipulation via `push_event` is the idiomatic LiveView solution for this edge case.
|
||||
|
||||
**3. Fuzzy Search Implementation**
|
||||
**3. Keyboard Navigation: Hybrid Approach**
|
||||
Implemented keyboard accessibility with **mostly Server-Side + minimal Client-Side**:
|
||||
|
||||
```elixir
|
||||
# Server-Side: Navigation and Selection (~45 lines)
|
||||
def handle_event("member_dropdown_keydown", %{"key" => "ArrowDown"}, socket) do
|
||||
# Focus management on server
|
||||
new_index = min(current + 1, max_index)
|
||||
{:noreply, assign(socket, focused_member_index: new_index)}
|
||||
end
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Client-Side: Only preventDefault for Enter in forms (~13 lines)
|
||||
Hooks.ComboBox = {
|
||||
mounted() {
|
||||
this.el.addEventListener("keydown", (e) => {
|
||||
const isDropdownOpen = this.el.getAttribute("aria-expanded") === "true"
|
||||
if (e.key === "Enter" && isDropdownOpen) {
|
||||
e.preventDefault() // Prevent form submission
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale:**
|
||||
- Server-Side handles all navigation logic → simpler, testable, follows LiveView best practices
|
||||
- Client-Side only prevents browser default behavior (form submit on Enter)
|
||||
- Latency (~20-50ms) is imperceptible for keyboard events without DB queries
|
||||
- Follows CODE_GUIDELINES "Minimal JavaScript Philosophy"
|
||||
|
||||
**Alternative Considered:** Full Client-Side with JavaScript Hook (~80 lines)
|
||||
- ❌ More complex code
|
||||
- ❌ State synchronization between client/server
|
||||
- ✅ Zero latency (but not noticeable in practice)
|
||||
- **Decision:** Server-Side approach is simpler and sufficient
|
||||
|
||||
**4. Fuzzy Search Implementation**
|
||||
Combined PostgreSQL Full-Text Search + Trigram for optimal results:
|
||||
```sql
|
||||
-- FTS for exact word matching
|
||||
|
|
@ -1393,11 +1432,13 @@ end
|
|||
- ✅ Direct DOM manipulation (autocomplete, input values)
|
||||
- ✅ Browser APIs (clipboard, geolocation)
|
||||
- ✅ Third-party libraries
|
||||
- ✅ Preventing browser default behaviors (form submit, scroll)
|
||||
|
||||
**When NOT to use JavaScript:**
|
||||
- ❌ Form submissions
|
||||
- ❌ Simple show/hide logic
|
||||
- ❌ Server-side data fetching
|
||||
- ❌ Keyboard navigation logic (can be done server-side efficiently)
|
||||
|
||||
**Pattern:**
|
||||
```elixir
|
||||
|
|
@ -1407,6 +1448,12 @@ socket |> push_event("event-name", %{key: value})
|
|||
window.addEventListener("phx:event-name", (e) => { /* handle */ })
|
||||
```
|
||||
|
||||
**Keyboard Events Pattern:**
|
||||
For keyboard navigation in forms, use hybrid approach:
|
||||
- Server handles navigation logic via `phx-window-keydown`
|
||||
- Minimal hook only for `preventDefault()` to avoid form submit conflicts
|
||||
- Result: ~13 lines JS vs ~80 lines for full client-side solution
|
||||
|
||||
#### 3. PostgreSQL Trigram Search
|
||||
Requires `pg_trgm` extension with GIN indexes:
|
||||
```sql
|
||||
|
|
@ -1418,7 +1465,34 @@ Supports:
|
|||
- Partial matching: "Mit" finds "Mitglied"
|
||||
- Substring: "exam" finds "example.com"
|
||||
|
||||
#### 4. Test-Driven Development for Bug Fixes
|
||||
#### 4. Server-Side Keyboard Navigation Performance
|
||||
**Challenge:** Concern that server-side keyboard events would feel laggy.
|
||||
|
||||
**Reality Check:**
|
||||
- LiveView roundtrip: ~20-50ms on decent connection
|
||||
- Human perception threshold: ~100ms
|
||||
- Result: **Feels instant** in practice
|
||||
|
||||
**Why it works:**
|
||||
```elixir
|
||||
# Event handler only updates index (no DB queries)
|
||||
def handle_event("member_dropdown_keydown", %{"key" => "ArrowDown"}, socket) do
|
||||
new_index = min(socket.assigns.focused_member_index + 1, max_index)
|
||||
{:noreply, assign(socket, focused_member_index: new_index)}
|
||||
end
|
||||
```
|
||||
- No database queries
|
||||
- No complex computations
|
||||
- Just state updates → extremely fast
|
||||
|
||||
**When to use Client-Side instead:**
|
||||
- Complex animations (Canvas, WebGL)
|
||||
- Real-time gaming
|
||||
- Continuous interactions (drag & drop, drawing)
|
||||
|
||||
**Lesson:** Don't prematurely optimize for latency. Server-side is simpler and often sufficient.
|
||||
|
||||
#### 5. Test-Driven Development for Bug Fixes
|
||||
Effective workflow:
|
||||
1. Write test that reproduces bug (should fail)
|
||||
2. Implement minimal fix
|
||||
|
|
@ -1435,7 +1509,8 @@ Effective workflow:
|
|||
- `lib/mv_web/live/user_live/form.ex` - Event handlers, state management
|
||||
|
||||
**Frontend:**
|
||||
- `assets/js/app.js` - Input value hook (6 lines)
|
||||
- `assets/js/app.js` - Input value hook (6 lines) + ComboBox hook (13 lines)
|
||||
- `lib/mv_web/live/user_live/form.ex` - Keyboard event handlers, focus management
|
||||
- `priv/gettext/**/*.po` - 10 new translation keys (DE/EN)
|
||||
|
||||
**Tests (NEW):**
|
||||
|
|
@ -1472,14 +1547,14 @@ This project demonstrates a modern Phoenix application built with:
|
|||
**Next Steps:**
|
||||
- Implement roles & permissions
|
||||
- Add payment tracking
|
||||
- Improve accessibility (WCAG 2.1 AA)
|
||||
- ✅ ~~Improve accessibility (WCAG 2.1 AA)~~ - Keyboard navigation implemented
|
||||
- Member self-service portal
|
||||
- Email communication features
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.1
|
||||
**Last Updated:** 2025-11-13
|
||||
**Document Version:** 1.2
|
||||
**Last Updated:** 2025-11-27
|
||||
**Maintainer:** Development Team
|
||||
**Status:** Living Document (update as project evolves)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue