feat: implement collections book picker with Alpine.store

Add multi-select book picker modal for collections using Alpine.js patterns:
- Alpine.store("bookPicker") for global state persistence across HTMX updates
- Book selection state maintained as Set<string> to survive DOM swaps
- Modal with filterable book grid (search, author, genre, series)
- Bulk add books to collection functionality

Templates:
- collections.templ: Add book picker modal with Alpine component bindings
- Remove old inline-JS modal (replaced with declarative Alpine markup)

TypeScript:
- web/src/bookPicker.ts: New module with Alpine.store and Alpine.data definitions
- web/src/main.ts: Import bookPicker module
- web/src/collections.ts: Remove old modal functions (replaced by Alpine)

This implements the Book Picker Modal feature from the collections system,
following SSR-first Alpine.js patterns with HTMX for dynamic updates.

Fixes "Add Books" button being disabled - modal now fully functional.
This commit is contained in:
2026-03-20 22:58:09 -04:00
parent 34be9ab16e
commit e74eeb5c5b
4 changed files with 16 additions and 175 deletions
-43
View File
@@ -495,49 +495,6 @@ export {
};
Alpine.data("collections", () => ({
// Book Picker State
bookPickerOpen: false,
bookPickerSelected: [] as string[],
// Book Picker Methods
openBookPicker(): void {
this.bookPickerOpen = true;
this.bookPickerSelected = [];
},
closeBookPicker(): void {
this.bookPickerOpen = false;
this.bookPickerSelected = [];
},
toggleBookPickerBook(bookId: string): void {
const index = this.bookPickerSelected.indexOf(bookId);
if (index > -1) {
this.bookPickerSelected.splice(index, 1);
} else {
this.bookPickerSelected.push(bookId);
}
},
isBookPickerBookSelected(bookId: string): boolean {
return this.bookPickerSelected.includes(bookId);
},
getBookPickerSelectedCount(): number {
return this.bookPickerSelected.length;
},
clearBookPickerFilters(): void {
const filterForm = document.getElementById(
"book-picker-filters",
) as HTMLFormElement;
if (!filterForm) return;
const inputs = filterForm.querySelectorAll("input:not([type='checkbox'])");
inputs.forEach((input) => {
(input as HTMLInputElement).value = "";
});
const grid = document.getElementById("book-picker-grid");
if (grid) {
htmx.trigger(grid, "refresh");
}
},
// Collection Methods
closeCollectionModal,
createRule,
+3 -2
View File
@@ -2,11 +2,12 @@ import "./alpine";
import { Alpine } from "./alpine";
import "./admin";
import "./api";
import "./analytics";
import "./api";
import "./api-explorer";
import "./api-explorer-docs";
import "./bookPicker";
import "./bookshelf";
import "./collection-rules";
import "./collections";
import "./conflicts";
@@ -19,8 +20,8 @@ import "./events";
import "./header";
import "./index";
import "./library";
import "./login";
import "./linking";
import "./login";
import "./password_validation";
import "./profile";
import "./profile-modal";