UNPKG

react-native-sortables

Version:

Powerful Sortable Components for Flexible Content Reordering in React Native

109 lines (85 loc) 3.07 kB
import type { ReactNode } from 'react'; import type { RenderItem } from '../../../types'; export type Store<I> = { getKeys: () => Array<string>; subscribeKeys: (listener: () => void) => () => void; getNode: (key: string) => ReactNode | undefined; subscribeItem: (key: string, listener: () => void) => () => void; update: ( entries: Array<[key: string, item: I]>, renderItem?: RenderItem<I> ) => void; }; const shallowEq = (a: Array<string>, b: Array<string>) => a.length === b.length && a.every((v, i) => v === b[i]); export function createItemsStore<I>( initialItems?: Array<[string, I]>, initialRenderItem?: RenderItem<I> ): Store<I> { let keys: Array<string> = []; const nodes = new Map<string, ReactNode>(); const meta = new Map<string, { item: I; index: number }>(); const keyListeners = new Set<() => void>(); const itemListeners = new Map<string, Set<() => void>>(); // Track the renderer to detect changes let currentRenderer: RenderItem<I> | undefined = initialRenderItem; const getKeys = () => keys; const getNode = (key: string) => nodes.get(key); const subscribeKeys = (listener: () => void) => { keyListeners.add(listener); return () => keyListeners.delete(listener); }; const subscribeItem = (key: string, listener: () => void) => { let set = itemListeners.get(key); if (!set) itemListeners.set(key, (set = new Set())); set.add(listener); return () => { set.delete(listener); if (!set.size) itemListeners.delete(key); }; }; // Core logic for init + updates function apply( entries: Array<[string, I]>, renderItem?: RenderItem<I>, notify = true ) { const nextKeys = entries.map(([k]) => k); const keysChanged = !shallowEq(keys, nextKeys); // If renderer changed, we’ll force-recompute all items const rendererChanged = renderItem !== currentRenderer; currentRenderer = renderItem; if (keysChanged) { const nextSet = new Set(nextKeys); for (const k of keys) { if (!nextSet.has(k)) { meta.delete(k); nodes.delete(k); } } keys = nextKeys; } const touched = new Set<string>(); entries.forEach(([k, item], index) => { const prev = meta.get(k); const changed = rendererChanged || !prev || prev.item !== item || prev.index !== index; if (!changed) return; const info = { index, item }; meta.set(k, info); nodes.set(k, renderItem ? renderItem(info) : (item as ReactNode)); touched.add(k); }); if (!notify) return; if (keysChanged) keyListeners.forEach(fn => fn()); touched.forEach(k => { const subs = itemListeners.get(k); if (subs) subs.forEach(fn => fn()); }); } // Initial snapshot (sync), no notifications if (initialItems) apply(initialItems, initialRenderItem, false); const update: Store<I>['update'] = (entries, renderItem) => apply(entries, renderItem, true); return { getKeys, getNode, subscribeItem, subscribeKeys, update }; }