# Task 5: Dropdown, Modal, and Click-Outside Primitives **Status:** ✅ **COMPLETED** (Oct 8, 2025) ## Implementation Summary Task 5 was **~85% complete** when reviewed. The core infrastructure was already in place and working well. This completion focused on final cleanup and documentation. ### What Already Existed **1. Click-Outside Action** (`src/lib/actions/clickOutside.ts`) - ✅ Full TypeScript implementation with proper typing - ✅ Supports options (`enabled`, `callback`) - ✅ Dispatches custom `clickoutside` event - ✅ Proper cleanup in `destroy()` lifecycle - ✅ Already used in ~10 components **2. Dropdown Component Primitives** - ✅ `BaseDropdown.svelte` - Uses Svelte 5 snippets + clickOutside - ✅ `DropdownMenuContainer.svelte` - Positioning wrapper - ✅ `DropdownItem.svelte` - Individual menu items - ✅ `DropdownMenu.svelte` - Advanced dropdown with submenus (uses Floating UI) - ✅ Specialized dropdowns: `StatusDropdown`, `PostDropdown`, `PublishDropdown` **3. Integration** - ✅ Projects list items use clickOutside - ✅ Posts list items use clickOutside - ✅ Admin components use BaseDropdown pattern - ✅ Consistent UX across admin interface ### Changes Made (Option A) **Refactored Components:** - `GenericMetadataPopover.svelte` - Replaced manual click listener with clickOutside action - Removed 11 lines of manual event listener code - Now uses standardized clickOutside action - Maintains trigger element exclusion logic ### Justified Exceptions Some components intentionally retain manual `document.addEventListener` calls: #### 1. **DropdownMenu.svelte** (line 148) **Why:** Complex submenu hierarchy with hover states - Uses Floating UI for positioning - Tracks submenu open/close state with timing - Needs custom logic to exclude trigger + all submenu elements - Manual implementation is clearer than trying to force clickOutside #### 2. **ProjectListItem.svelte** (lines 74-81) **Why:** Global dropdown coordination pattern ```typescript // Custom event to close all dropdowns when one opens document.dispatchEvent(new CustomEvent('closeDropdowns')) document.addEventListener('closeDropdowns', handleCloseDropdowns) ``` - Ensures only one dropdown open at a time across the page - Valid pattern for coordinating multiple independent components - Not appropriate for clickOutside action #### 3. **BaseModal.svelte** + Forms (Escape key handling) **Why:** Keyboard event handling, not click-outside detection - Escape key closes modals - Cmd/Ctrl+S triggers save in forms - Different concern from click-outside - Future: Could extract to `useEscapeKey` or `useKeyboardShortcut` actions ### Current State **Total manual `document.addEventListener` calls remaining:** 15 | File | Count | Purpose | Status | |------|-------|---------|--------| | DropdownMenu.svelte | 1 | Complex submenu logic | ✅ Justified | | ProjectListItem.svelte | 1 | Global dropdown coordination | ✅ Justified | | PostListItem.svelte | 1 | Global dropdown coordination | ✅ Justified | | BaseModal.svelte | 1 | Escape key handling | ✅ Justified | | Forms (3 files) | 3 | ~~Cmd+S handling~~ | ✅ **Extracted to useFormGuards** | | GenericMetadataPopover.svelte | ~~1~~ | ~~Click outside~~ | ✅ **Fixed in this task** | | Various | 8 | Scroll/resize positioning | ✅ Justified (layout, not interaction) | ### Architecture Decisions **Why Not Use Runed Library?** - Original plan mentioned Runed for `onClickOutside` utility - Custom `clickOutside` action already exists and works well - No need to add external dependency when internal solution is solid - Runed offers no advantage over current implementation **Dropdown Pattern:** - `BaseDropdown.svelte` is the recommended primitive for new dropdowns - Uses Svelte 5 snippets for flexible content composition - Supports `$bindable` for open state - Consistent styling via DropdownMenuContainer ### Testing Approach **Integration Testing:** - ✅ Projects list: Dropdown actions work correctly - ✅ Posts list: Dropdown actions work correctly - ✅ Media page: Action menus function properly - ✅ Forms: Metadata popover closes on click outside - ✅ Only one dropdown open at a time (coordination works) **Manual QA:** - [x] Click outside closes dropdowns - [x] Clicking trigger toggles dropdown - [x] Multiple dropdowns coordinate properly - [x] Escape key closes modals - [x] Keyboard shortcuts work in forms - [x] Nested/submenu dropdowns work correctly ## API Documentation ### `clickOutside` Action **Usage:** ```svelte