function createPlanToday({ identity, save, start, limit = 5 }) { let openState = false; let draftIds = []; let itemsById = new Map(); function cleanItems(items) { const unique = new Map(); for (const item of items || []) { const id = identity?.(item); if (id && !unique.has(id)) unique.set(id, item); } return unique; } function open(selectedItems, candidates) { itemsById = cleanItems([...(selectedItems || []), ...(candidates || [])]); draftIds = []; for (const item of selectedItems || []) { const id = identity?.(item); if (id && itemsById.has(id) && !draftIds.includes(id) && draftIds.length < limit) draftIds.push(id); } openState = true; return snapshot(); } function toggle(item) { if (!openState) return 'closed'; const id = identity?.(item); if (!id) return 'unavailable'; itemsById.set(id, item); const index = draftIds.indexOf(id); if (index >= 0) { draftIds.splice(index, 1); return 'removed'; } if (draftIds.length >= limit) return 'full'; draftIds.push(id); return 'added'; } function move(id, direction) { const index = draftIds.indexOf(id); const target = direction === 'up' ? index - 1 : direction === 'down' ? index + 1 : -1; if (!openState || index < 0 || target < 0 || target >= draftIds.length) return false; [draftIds[index], draftIds[target]] = [draftIds[target], draftIds[index]]; return true; } function close() { openState = false; draftIds = []; itemsById = new Map(); } function cancel() { close(); return true; } function commit({ start: startAfterSave = false } = {}) { if (!openState) return 'closed'; const ids = [...draftIds]; if (save?.(ids) === false) return 'unavailable'; const first = ids.length ? itemsById.get(ids[0]) : null; close(); if (startAfterSave && first) start?.(first); return 'saved'; } function snapshot() { return { open: openState, ids: [...draftIds], count: draftIds.length, limit }; } function item(id) { return itemsById.get(id) || null; } function candidates() { return [...itemsById.entries()].filter(([id]) => !draftIds.includes(id)).map(([, value]) => value); } return { open, toggle, move, cancel, commit, snapshot, item, candidates }; } if (typeof module !== 'undefined' && module.exports) module.exports = createPlanToday;