/// /** * TEST PATTERN: Cell.equals() vs Manual IDs * * WHAT THIS TESTS: * This pattern demonstrates that the framework tracks object identity internally, * and manual ID generation is unnecessary. Using Cell.equals() for item comparison * is the recommended approach. * * MANUAL VERIFICATION STEPS: * 1. Click "Add Item" several times to add items to the list * 2. Click on different items to select them * 3. Verify that: * - Only one item is highlighted at a time (green background) * - The selected item title appears at the top * - Clicking a selected item deselects it * 4. Click "Remove Selected" to remove the selected item * 5. Verify the item is removed and selection clears * 6. Select an item, then add a new item at the start * 7. Verify the selected item remains correctly highlighted * * WHAT CONFIRMS IT WORKS: * - Items can be selected/deselected by clicking * - Selection highlighting follows the correct item even after list changes * - Items can be removed using Cell.equals() to find them * - No manual ID generation is needed for any of this to work * - The pattern uses object references (cells) instead of string IDs */ import { Cell, computed, handler, ifElse, NAME, pattern, UI, } from "commontools"; interface Item { title: string; description: string; } interface TestCellEqualsInput { items: Cell; selectedItem: Cell; } interface TestCellEqualsOutput extends TestCellEqualsInput {} // Handler to add a new item at the END of the list // (Adding at end keeps indices stable for existing items) const addItem = handler }>( (_, { items }) => { const timestamp = new Date().toLocaleTimeString(); const newItem: Item = { title: `Item ${timestamp}`, description: `Created at ${timestamp}`, }; items.push(newItem); }, ); // Handler to select an item by index (or deselect if already selected) // We use index because the workaround pre-computes items in a computed(), // which gives us plain values instead of cell references. const selectItem = handler< unknown, { selectedItem: Cell; items: Cell; index: number } >( (_, { selectedItem, items, index }) => { const current = selectedItem.get(); const targetItem = items.get()[index]; if (!targetItem) return; // Use Cell.equals() to check if this item is already selected // This tests the core claim: Cell.equals() can identify items // without needing manual IDs if (current && Cell.equals(current, targetItem)) { // Deselect if clicking the same item selectedItem.set(null); } else { // Select the clicked item selectedItem.set(targetItem); } }, ); // Handler to remove the selected item const removeSelected = handler< unknown, { items: Cell; selectedItem: Cell } >( (_, { items, selectedItem }) => { const selected = selectedItem.get(); if (!selected) return; const current = items.get(); // Use Cell.equals() to find the item in the array const index = current.findIndex((el) => Cell.equals(selected, el)); if (index >= 0) { items.set(current.toSpliced(index, 1)); selectedItem.set(null); } }, ); export default pattern( ({ items, selectedItem }) => { // Create computed values for display const hasSelection = computed(() => selectedItem.get() !== null); const selectedTitle = computed(() => { const selected = selectedItem.get(); return selected ? selected.title : ""; }); // WORKAROUND: Pre-compute selection state outside the map callback // This avoids the "Cannot create cell link - space required" error // that occurs when closing over cells inside .map() callbacks const itemsWithSelection = computed(() => { const selected = selectedItem.get(); return items.get().map((item, index) => ({ item, index, isSelected: selected !== null && Cell.equals(selected, item), })); }); return { [NAME]: "Test Cell.equals()", [UI]: (

Cell.equals() Test Pattern

This pattern demonstrates using Cell.equals() for item identification instead of manual ID generation.

{/* Controls */}
Add Item Remove Selected
{/* Selected item display */} {ifElse( hasSelection,
Selected: {selectedTitle}
,
No item selected
, )} {/* Items list - using pre-computed selection state */}
{itemsWithSelection.map((entry) => (
{entry.item.title}
{entry.item.description}
))}
{/* Show count */}
Total items: {computed(() => items.get().length)}
), items, selectedItem, }; }, );