Multiple canvases
Each GridProvider is independent: its own layout, selection, gestures, and options. Put as many on a page as you need. They only interact when you wrap them in a GridTransferScope.
Acceptance rules
acceptTransfers on a provider is true by default. Set it to false to make a canvas a source only, or to a predicate (item, sourceId) => boolean to decide per item. The predicate runs on every pointer move over the canvas, so keep it cheap.
Scale differences
Canvases can be rendered at different scales (a 1200px layout in a 600px element next to a 400px layout in a 400px element). The scope converts the dragged item's size through on-screen pixels so it keeps the same visual size when it crosses over, then the target's placeItem clamps it to fit. Pass size explicitly through the core transferItem if you need different behavior.
Nested canvases
A GridProvider may render inside a GridItem of another provider, and both may share a scope. While the pointer is still inside the source canvas, only its descendants can become targets; siblings and ancestors wait until the pointer leaves. Among candidates, the deepest element wins, then the smallest. This is what makes "drag out of a group into the page, or into a sibling group" work without special cases.
Identifying canvases
Provider ids are generated with useId() and exposed through useGridContext().id; onTransferOut(itemId, targetId) and onTransferIn(item, sourceId) use them. Keep your own map from provider id to lane if you need names.