195 lines
6.7 KiB
Markdown
195 lines
6.7 KiB
Markdown
|
|
# Fuzzy Search Filter System
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The calendar includes a powerful fuzzy search filter system that allows users to quickly find events by typing partial matches of event titles or descriptions. The system dims non-matching events while keeping matching events fully visible, providing instant visual feedback.
|
||
|
|
|
||
|
|
## Features
|
||
|
|
|
||
|
|
- **Real-time fuzzy search** using Fuse.js library
|
||
|
|
- **Visual filtering** with opacity-based dimming (0.2 for non-matches, 1.0 for matches)
|
||
|
|
- **Minimum character threshold** (2 characters) handled by Fuse.js configuration
|
||
|
|
- **Cross-navigation persistence** - filter applies to pre-rendered grids during navigation
|
||
|
|
- **Escape key support** for quick filter clearing
|
||
|
|
- **Performance optimized** with requestAnimationFrame debouncing
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
### Core Components
|
||
|
|
|
||
|
|
#### EventFilterManager (`src/managers/EventFilterManager.ts`)
|
||
|
|
Central component responsible for:
|
||
|
|
- Managing search input event listeners
|
||
|
|
- Performing fuzzy search with Fuse.js
|
||
|
|
- Coordinating visual updates across all event containers
|
||
|
|
- Emitting filter change events via EventBus
|
||
|
|
|
||
|
|
#### CSS-based Visual System
|
||
|
|
```css
|
||
|
|
/* Dim all events when filter is active */
|
||
|
|
swp-events-layer[data-filter-active="true"] swp-event {
|
||
|
|
opacity: 0.2;
|
||
|
|
transition: opacity 200ms ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Keep matching events fully visible */
|
||
|
|
swp-events-layer[data-filter-active="true"] swp-event[data-matches="true"] {
|
||
|
|
opacity: 1;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Pre-rendered Grid Support
|
||
|
|
- NavigationManager listens for `FILTER_CHANGED` events
|
||
|
|
- NavigationRenderer applies filter state to all grid containers
|
||
|
|
- Ensures filter persists when navigating between time periods
|
||
|
|
|
||
|
|
### Event Flow
|
||
|
|
|
||
|
|
1. **User Input** → Search field receives input
|
||
|
|
2. **Debounced Processing** → RequestAnimationFrame prevents excessive filtering
|
||
|
|
3. **Fuzzy Search** → Fuse.js performs search against event titles/descriptions
|
||
|
|
4. **DOM Updates** → Matching events marked with `data-matches="true"`
|
||
|
|
5. **CSS Application** → Non-matching events automatically dimmed via CSS rules
|
||
|
|
6. **Cross-Grid Sync** → Filter applied to all pre-rendered grids
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
### Fuse.js Settings
|
||
|
|
```typescript
|
||
|
|
const fuseOptions = {
|
||
|
|
keys: ['title', 'description'], // Search in title and description
|
||
|
|
threshold: 0.3, // 0 = exact match, 1 = match anything
|
||
|
|
includeScore: true, // Include relevance scores
|
||
|
|
minMatchCharLength: 2, // Minimum 2 characters for match
|
||
|
|
shouldSort: true, // Sort by relevance
|
||
|
|
ignoreLocation: true // Search anywhere in string
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### CSS Variables
|
||
|
|
```css
|
||
|
|
:root {
|
||
|
|
--filter-dimmed-opacity: 0.2; /* Opacity for non-matching events */
|
||
|
|
--filter-transition: 200ms ease; /* Smooth opacity transitions */
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Integration Points
|
||
|
|
|
||
|
|
### CalendarManager
|
||
|
|
```typescript
|
||
|
|
// EventFilterManager is initialized in CalendarManager constructor
|
||
|
|
this.eventFilterManager = new EventFilterManager();
|
||
|
|
```
|
||
|
|
|
||
|
|
### EventRenderer
|
||
|
|
```typescript
|
||
|
|
// Events emit EVENTS_RENDERED for filter system to track
|
||
|
|
this.eventBus.emit(CoreEvents.EVENTS_RENDERED, {
|
||
|
|
events: events,
|
||
|
|
container: context.container
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
### HTML Structure
|
||
|
|
```html
|
||
|
|
<swp-search-container>
|
||
|
|
<input type="search" placeholder="Search events..." />
|
||
|
|
</swp-search-container>
|
||
|
|
```
|
||
|
|
|
||
|
|
## User Experience
|
||
|
|
|
||
|
|
### Search Behavior
|
||
|
|
- **No input**: All events visible at full opacity
|
||
|
|
- **1 character**: Filter active, but Fuse.js won't return matches (by design)
|
||
|
|
- **2+ characters**: Real-time fuzzy matching with visual feedback
|
||
|
|
- **Empty field**: Filter cleared, all events return to normal
|
||
|
|
- **Escape key**: Immediate filter clearing
|
||
|
|
|
||
|
|
### Visual Feedback
|
||
|
|
- **Search field styling**: Border color changes when filter is active
|
||
|
|
- **Event dimming**: Non-matching events fade to 20% opacity
|
||
|
|
- **Smooth transitions**: 200ms ease transitions for polished feel
|
||
|
|
|
||
|
|
## Performance Considerations
|
||
|
|
|
||
|
|
### Optimization Strategies
|
||
|
|
1. **RequestAnimationFrame debouncing** prevents excessive search operations
|
||
|
|
2. **CSS-based visual updates** leverage browser's rendering pipeline
|
||
|
|
3. **Event delegation** avoids per-event listener overhead
|
||
|
|
4. **Efficient DOM queries** using attribute selectors
|
||
|
|
|
||
|
|
### Memory Management
|
||
|
|
- Event listeners properly scoped to component lifecycle
|
||
|
|
- Fuse.js instance recreated only when event data changes
|
||
|
|
- Minimal DOM manipulation through attribute-based CSS rules
|
||
|
|
|
||
|
|
## Error Handling
|
||
|
|
|
||
|
|
### Graceful Degradation
|
||
|
|
- System continues to function if Fuse.js fails to load
|
||
|
|
- Search input remains functional for basic text entry
|
||
|
|
- Console warnings for debugging (removed in production)
|
||
|
|
|
||
|
|
### Edge Cases
|
||
|
|
- **No events to search**: Filter system remains inactive
|
||
|
|
- **Empty search results**: All events dimmed (expected behavior)
|
||
|
|
- **DOM timing issues**: Waits for DOM readiness before initialization
|
||
|
|
|
||
|
|
## Future Enhancements
|
||
|
|
|
||
|
|
### Potential Improvements
|
||
|
|
- **Search result highlighting** within event text
|
||
|
|
- **Advanced filters** (date range, event type, etc.)
|
||
|
|
- **Search history** with dropdown suggestions
|
||
|
|
- **Keyboard navigation** through search results
|
||
|
|
- **Search analytics** for usage insights
|
||
|
|
|
||
|
|
### Performance Optimizations
|
||
|
|
- **Virtual scrolling** for large event sets
|
||
|
|
- **Search index caching** for repeated queries
|
||
|
|
- **Web Workers** for heavy search operations
|
||
|
|
|
||
|
|
## Dependencies
|
||
|
|
|
||
|
|
### External Libraries
|
||
|
|
- **Fuse.js** (v7.x) - Fuzzy search library
|
||
|
|
- License: Apache 2.0
|
||
|
|
- File: `wwwroot/js/lib/fuse.min.mjs`
|
||
|
|
- Import: ES module format
|
||
|
|
|
||
|
|
### Internal Dependencies
|
||
|
|
- **EventBus** - Inter-component communication
|
||
|
|
- **CoreEvents** - Centralized event type definitions
|
||
|
|
- **CalendarEvent** - Event data type definitions
|
||
|
|
|
||
|
|
## Testing Considerations
|
||
|
|
|
||
|
|
### Test Scenarios
|
||
|
|
1. **Basic search functionality** - Verify fuzzy matching works
|
||
|
|
2. **Performance testing** - Large event sets (1000+ events)
|
||
|
|
3. **Cross-navigation** - Filter persistence during navigation
|
||
|
|
4. **Edge cases** - Empty results, special characters, very long queries
|
||
|
|
5. **Accessibility** - Screen reader compatibility, keyboard navigation
|
||
|
|
|
||
|
|
### Browser Compatibility
|
||
|
|
- **Modern browsers** with ES modules support
|
||
|
|
- **CSS custom properties** support required
|
||
|
|
- **requestAnimationFrame** API required
|
||
|
|
|
||
|
|
## Maintenance Notes
|
||
|
|
|
||
|
|
### Code Organization
|
||
|
|
- **Single responsibility** - EventFilterManager handles only filtering
|
||
|
|
- **Event-driven** - Loose coupling via EventBus
|
||
|
|
- **CSS-first** - Visual logic in stylesheets, not JavaScript
|
||
|
|
- **Type-safe** - Full TypeScript implementation
|
||
|
|
|
||
|
|
### Common Issues
|
||
|
|
- **Timing**: Ensure EventFilterManager initializes after DOM ready
|
||
|
|
- **Event data**: Verify EVENTS_RENDERED events are emitted correctly
|
||
|
|
- **CSS specificity**: Filter styles must override existing event styles
|
||
|
|
- **Memory leaks**: Properly clean up event listeners in destroy()
|
||
|
|
|
||
|
|
This fuzzy search filter system provides an intuitive and performant way for users to quickly locate specific events within the calendar interface.
|