a small incremental UI library for the web
javascript web ui
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement drag/drop by collecting downstream elements into a ref

garrison 8a5b50b3 c971d54d

+69 -17
+69 -17
demos/todo/todo.tsx
··· 41 41 ]); 42 42 const [newItemText, setNewItemText] = useState(''); 43 43 44 + const itemElementsRef = useRef(null); 45 + itemElementsRef.current = new Array(items.length).fill(null); 46 + 47 + const dragStatusRef = useRef(false); 48 + const closestRef = useRef(null); 49 + const [closest, setClosest] = useState(null); 50 + 51 + const handleDragOver = (ev) => { 52 + if (dragStatusRef.current) { 53 + const elements = itemElementsRef.current; 54 + 55 + const {clientY: mouseY} = ev; 56 + 57 + let closestIndex = null; 58 + let closestTop = null; 59 + let closestDistance = Infinity; 60 + for (let i = 0; i < elements.length; i++) { 61 + const {y: elY, height: elHeight} = elements[i].current.getBoundingClientRect(); 62 + 63 + const topDist = Math.abs(elY - mouseY); 64 + if (topDist < closestDistance) { 65 + closestIndex = i; 66 + closestTop = true; 67 + closestDistance = topDist; 68 + } 69 + const bottomDist = Math.abs((elY + elHeight) - mouseY); 70 + if (bottomDist < closestDistance) { 71 + closestIndex = i; 72 + closestTop = false; 73 + closestDistance = bottomDist; 74 + } 75 + } 76 + 77 + if (!closestRef.current || closestRef.current.i !== closestIndex || closestRef.current.top !== closestTop) { 78 + closestRef.current = {i: closestIndex, top: closestTop}; 79 + setClosest(closestRef.current); 80 + } 81 + } 82 + }; 83 + 84 + useEffect(() => { 85 + document.addEventListener('dragover', handleDragOver); 86 + return () => document.removeEventListener('dragover', handleDragOver); 87 + }); 88 + 44 89 function toggleComplete(id) { 45 90 setItems(items => { 46 91 return items.map(item => { ··· 100 145 <div>{completeCount} items completed</div> 101 146 <button disabled={completeCount === 0} onClick={clearComplete}>Clear completed</button> 102 147 </div> 103 - <div class="items">{items.map(item => <Item {...{item, toggleComplete}} />)}</div> 148 + <div 149 + class="items" 150 + onDragStart={ev => { 151 + dragStatusRef.current = true; 152 + closestRef.current = null; 153 + setClosest(null); 154 + }} 155 + onDragEnd={ev => { 156 + dragStatusRef.current = false; 157 + closestRef.current = null; 158 + setClosest(null); 159 + }} 160 + > 161 + {items.map((item, i) => <Item {...{item, i, toggleComplete, itemElementsRef, closest}} />)} 162 + </div> 104 163 </div> 105 164 ); 106 165 } ··· 155 214 ); 156 215 } 157 216 158 - function Item({item, toggleComplete}) { 159 - const [dropStatus, setDropStatus] = useState(null); 217 + function Item({item, i, toggleComplete, itemElementsRef, closest}) { 160 218 const labelRef = useRef(null); 219 + itemElementsRef.current[i] = labelRef; 220 + 221 + let dropClass = null; 222 + if (closest && closest.i === i) { 223 + if (closest.top) dropClass = 'top'; 224 + else dropClass = 'bottom'; 225 + } 161 226 162 227 useStyle(` 163 228 label { ··· 190 255 return ( 191 256 <label 192 257 ref={labelRef} 193 - class={dropStatus} 258 + class={dropClass} 194 259 draggable="true" 195 - onDragLeave={ev => { 196 - setDropStatus(null); 197 - }} 198 - onDragOver={ev => { 199 - const {clientY} = ev; 200 - const {y: elY, height: elHeight} = ev.target.getBoundingClientRect(); 201 - 202 - const y = clientY - elY; 203 - const top = y < (elHeight / 2); 204 - 205 - if (top) setDropStatus('top'); 206 - else setDropStatus('bottom'); 207 - }} 208 260 > 209 261 <input 210 262 type="checkbox"