Skip to content

Commit 36d6d65

Browse files
committed
refactor(breaking): converts the Overlay into a context and fixes rerenders caused by Overlay updates
1 parent b3e519d commit 36d6d65

File tree

8 files changed

+216
-115
lines changed

8 files changed

+216
-115
lines changed

README.md

Lines changed: 72 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
## What is DragSwag?
1818

1919
**DragSwag** is primarily a drag-and-drop library for React. Frustrated with the bulky APIs offered by other libraries,
20-
I developed DragSwag to focus on ergonomics and simplicity while maintaining flexibility and customization. Its built
20+
I developed DragSwag to focus on ergonomics and simplicity while maintaining flexibility and customization. It's built
2121
on top of dragswag/core, a universal building block suitable for any framework and vanilla JavaScript.
2222

2323
## Key Features
2424

25-
- **Extremely** simple—just use the useDraggable and useDroppable hooks, and the Overlay component to get started
25+
- **Extremely** simple—just use the useDraggable and useDroppable hooks, and the DragOverlayProvider to get started
2626
- **Highly** ergonomic—no need to memoize callbacks or configurations
2727
- **Fully** customizable—rich event system
2828
- **Two-way** data exchange between draggable and droppable components
@@ -72,12 +72,12 @@ bun add dragswag
7272
Show Me the Code!
7373

7474
Here is the simplest example involving two squares. The draggable square carries a color in its data. The droppable
75-
square reacts to the drag interaction by setting its color according to the draggables color. When dropped, the text of
75+
square reacts to the drag interaction by setting its color according to the draggable's color. When dropped, the text of
7676
the droppable square is updated.
7777

7878
<img width="400" alt="Simple drag-and-drop squares" src="https://raw.githubusercontent.com/pizzajsdev/dragswag/main/resources/drag-and-drop-squares.avif" />
7979

80-
The `DraggableSquare` component uses the `useDraggable` hook to make it draggable. The hooks configuration defines the
80+
The `DraggableSquare` component uses the `useDraggable` hook to make it draggable. The hook's configuration defines the
8181
kind and the data of the draggable. The draggable wrapper is used to make the component actually draggable:
8282

8383
```tsx
@@ -102,7 +102,7 @@ export const DraggableSquare = ({ color }: { color: string }) => {
102102

103103
The `DroppableSquare` component uses the `useDroppable` hook to make it droppable. The configuration defines the
104104
accepted kind and the callback for the `onDrop` event. The droppable wrapper is used to make the component droppable.
105-
The `hovered` property is used to get the data of the draggable when its hovered over:
105+
The `hovered` property is used to get the data of the draggable when it's hovered over:
106106

107107
```tsx
108108
import { useDroppable } from 'dragswag'
@@ -128,14 +128,14 @@ export const DroppableSquare = ({ color }: { color: string }) => {
128128
```
129129

130130
The `App` component renders the draggable and droppable squares. The draggable square is wrapped in an absolute wrapper
131-
to position it on the page. The `Overlay` component is rendered to show the dragged component:
131+
to position it on the page. The `DragOverlayProvider` component is needed to show and the dragged component:
132132

133133
```tsx
134-
import { Overlay } from 'dragswag'
134+
import { DragOverlayProvider } from 'dragswag'
135135

136136
export default function App() {
137137
return (
138-
<>
138+
<DragOverlayProvider>
139139
{/* Render squares with absolute wrappers for positioning */}
140140
<div style={{ position: 'relative' }}>
141141
<div style={{ position: 'absolute', top: 100, left: 100 }}>
@@ -145,9 +145,7 @@ export default function App() {
145145
<DroppableSquare color="green" />
146146
</div>
147147
</div>
148-
{/* Render overlay to show the dragged component */}
149-
<Overlay />
150-
</>
148+
</DragOverlayProvider>
151149
)
152150
}
153151
```
@@ -160,12 +158,12 @@ See more examples in the `examples` folder and in the [Examples](examples) secti
160158

161159
## How t works
162160

163-
Under the hood, DragSwag attaches a pointerdown event listener to draggable elements. After its triggered, it tracks
161+
Under the hood, DragSwag attaches a pointerdown event listener to draggable elements. After it's triggered, it tracks
164162
`pointermove` events on the document until `pointerup` occurs. On every `pointermove` event, it checks elements under
165163
the cursor using `document.elementsFromPoint()`, and then handles the logic of tracking current and new droppables at
166164
that point.
167165

168-
Draggables arent bound to the initial configuration, so it can be changed at any time, making it very flexible to use
166+
Draggables aren't bound to the initial configuration, so it can be changed at any time, making it very flexible to use
169167
new closures, settings, etc. The React bindings wrap this core logic and adapt some arguments to be more convenient.
170168

171169
An important point for React is the draggable/droppable wrappers—they keep the original ref to the React element and
@@ -236,7 +234,7 @@ There are three of them: `onDragStart`, `onDragMove`, and `onDragEnd`.
236234
### `onDragStart`
237235

238236
The callback is called when the drag interaction starts — this means the user clicked and started to move the element.
239-
In more detail, its called after the `shouldDrag` function returns `true`. Heres how it looks in the code:
237+
In more detail, it's called after the `shouldDrag` function returns `true`. Here's how it looks in the code:
240238

241239
```tsx
242240
const Square = () => {
@@ -250,7 +248,7 @@ const Square = () => {
250248
}
251249
```
252250

253-
The `props` here contain data related to the interaction: `data`, `event`, `dragStartEvent`, and `element`. Its quite
251+
The `props` here contain data related to the interaction: `data`, `event`, `dragStartEvent`, and `element`. It's quite
254252
intuitive — data is the `data` field in the configuration (or the result of the data factory function if specified),
255253
`event` is the PointerEvent from the `pointermove` handler, and the `dragStartEvent` is the `PointerEvent` from the
256254
`pointerdown` event.
@@ -264,7 +262,7 @@ This callback is also [described later](#ondragstart-1) in the configuration doc
264262

265263
### `onDragMove`
266264

267-
This callback is executed on every pointermove event. As you can imagine, its time-sensitive, so try to avoid putting
265+
This callback is executed on every pointermove event. As you can imagine, it's time-sensitive, so try to avoid putting
268266
expensive logic here.
269267

270268
Example:
@@ -283,7 +281,7 @@ const Square = () => {
283281

284282
Props contain all the same data as in the onDragStart callback, but with some additions:
285283

286-
- `dropTargets` is an array containing data about current drop targets under the cursor. Its an array, so if the
284+
- `dropTargets` is an array containing data about current drop targets under the cursor. It's an array, so if the
287285
current draggable is over multiple of them, they will be here. Each drop target is represented as an object with data
288286
and element fields. The `data` is the data field from the `useDroppable` configuration, allowing data exchange between
289287
draggable and droppable components. The `element` is the droppable element.
@@ -294,7 +292,7 @@ To get more of the idea of dropTargets and using their data, see the Data Transf
294292

295293
## Example - Data Transfer from Droppable to Draggable
296294

297-
Lets modify the draggable squares example to show how data can be transferred from the droppable to the draggable in
295+
Let's modify the draggable squares example to show how data can be transferred from the droppable to the draggable in
298296
the `onDragMove` callback.
299297

300298
First, we will change the` DraggableSquare.tsx` to the following:
@@ -336,7 +334,7 @@ export const DraggableSquare = ({ color: initialColor }: { color: string }) => {
336334
</details>
337335

338336
Here we have `onDragMove` and `onDragEnd` callbacks that control the color of the draggable square. When there is a drop
339-
target under the cursor (so `dropTargets.length > 0`), we get the color from the first drop targets data field.
337+
target under the cursor (so `dropTargets.length > 0`), we get the color from the first drop target's data field.
340338

341339
The `DroppableSquare.tsx` is mostly the same, but we add the data there and remove color matching logic for convenience:
342340

@@ -409,7 +407,7 @@ export const DroppableSquare = ({ color }: { color: string }) => {
409407
}
410408
```
411409

412-
When the droppable is hovered by the draggable, `hovered returns its data and kind; otherwise, its null.
410+
When the droppable is hovered by the draggable, `hovered returns its data and kind; otherwise, it's null.
413411

414412
Like the `draggable` wrapper, the component can be wrapped both in `draggable` and `droppable`; the order doesn't
415413
matter.
@@ -421,13 +419,13 @@ matter.
421419
The configuration of `useDroppable` can have the following callbacks: `onDragIn`, `onDragOut`, `onDragMove`, and
422420
`onDrop`.
423421

424-
Lets take a look at each of them.
422+
Let's take a look at each of them.
425423

426424
<hr />
427425

428426
### `onDragIn`
429427

430-
This callback is called when a draggable item enters the area of the drop target. Its executed once and can be used for
428+
This callback is called when a draggable item enters the area of the drop target. It's executed once and can be used for
431429
different interactions like changing color, setting some state, etc.
432430

433431
Here's an example:
@@ -459,11 +457,11 @@ area.
459457
### `onDragMove`
460458

461459
`onDragMove` is called on every `pointermove` event over the drop target. It can be used for customization of the drop
462-
targets look during the drag interaction.
460+
target's look during the drag interaction.
463461

464462
### Example - Dynamic Border on DroppableSquare
465463

466-
Lets modify the squares example to render a border on `DroppableSquare` depending on the position of the draggable.
464+
Let's modify the squares example to render a border on `DroppableSquare` depending on the position of the draggable.
467465

468466
The `DraggableSquare` will remain the same; the only changes will be in the `DroppableSquare component.
469467

@@ -540,10 +538,10 @@ will be showing the border. On `onDragOut` and `onDrop` events we remove the bor
540538

541539
### `onDragOut`
542540

543-
This callback is called when the draggable leaves the drop target area. Its often used in conjunction with `onDragIn`
541+
This callback is called when the draggable leaves the drop target area. It's often used in conjunction with `onDragIn`
544542
to perform opposite actions, like restoring initial state, color, or text.
545543

546-
You can see its usage in the previous example with dynamic borders — its used there to remove borders after the cursor
544+
You can see its usage in the previous example with dynamic borders — it's used there to remove borders after the cursor
547545
leaves the droppable square.
548546

549547
Arguments are mostly the same as in the previous callbacks; more details are in the [config docs](#ondragout-1).
@@ -707,7 +705,7 @@ const { draggable } = useDraggable({
707705
If `move` is false or not defined, the draggable component is cloned to the overlay layer, and the original component is
708706
shown as is.
709707

710-
Also, its important to note that the original component will not receive prop updates during the drag interaction —
708+
Also, it's important to note that the original component will not receive prop updates during the drag interaction —
711709
they are all applied to the dragging component.
712710

713711
`move` is ignored when the `placeholder` option is specified.
@@ -766,8 +764,8 @@ const { draggable } = useDraggable({
766764
})
767765
```
768766

769-
Note: Offset is calculated once when the drag interaction starts. Its the distance between the cursor position and the
770-
top-left corner of the dragging component. If not specified, its computed so that the components position matches its
767+
Note: Offset is calculated once when the drag interaction starts. It's the distance between the cursor position and the
768+
top-left corner of the dragging component. If not specified, it's computed so that the component's position matches its
771769
rendered position before the drag.
772770

773771
### Callbacks
@@ -786,7 +784,7 @@ const { draggable } = useDraggable({
786784
```
787785

788786
`shouldDrag` is called on every mouse move during the drag interaction until it returns `true` or the drag interaction
789-
ends. Its useful for adding a threshold or some other condition to start the drag interaction.
787+
ends. It's useful for adding a threshold or some other condition to start the drag interaction.
790788

791789
Arguments:
792790

@@ -833,8 +831,8 @@ const { draggable } = useDraggable({
833831
})
834832
```
835833

836-
The callback is called on every mouse move during the drag interaction. Its not recommended to put expensive logic here
837-
because its called frequently.
834+
The callback is called on every mouse move during the drag interaction. It's not recommended to put expensive logic here
835+
because it's called frequently.
838836

839837
Arguments are the same as in `onDragStart` with some additions:
840838

@@ -845,7 +843,7 @@ Arguments are the same as in `onDragStart` with some additions:
845843

846844
#### `onDragEnd`
847845

848-
Called when the drag interaction ends. `dropTargets` will be an empty array if the draggable wasnt dropped on any drop
846+
Called when the drag interaction ends. `dropTargets` will be an empty array if the draggable wasn't dropped on any drop
849847
target.
850848

851849
```tsx
@@ -861,7 +859,7 @@ const { draggable } = useDraggable({
861859

862860
### Full Example
863861

864-
Heres a complete example demonstrating the use of all the configuration options:
862+
Here's a complete example demonstrating the use of all the configuration options:
865863

866864
```tsx
867865
import { useDraggable } from 'dragswag'
@@ -1009,7 +1007,7 @@ Arguments are the same as in `onDragIn`.
10091007

10101008
#### `onDragMove`
10111009

1012-
Called when a draggable item moves within the droppable area. Its called on every mouse move during the drag
1010+
Called when a draggable item moves within the droppable area. It's called on every mouse move during the drag
10131011
interaction, so avoid putting expensive logic here.
10141012

10151013
```tsx
@@ -1042,7 +1040,7 @@ Arguments are the same as in `onDragIn`.
10421040

10431041
### Full Example
10441042

1045-
Heres a complete example demonstrating the use of all the configuration options:
1043+
Here's a complete example demonstrating the use of all the configuration options:
10461044

10471045
```tsx
10481046
import { useDroppable } from 'dragswag'
@@ -1072,6 +1070,44 @@ const DroppableSquare = () => {
10721070
}
10731071
```
10741072

1073+
### The `DragOverlayProvider`
1074+
1075+
The `DragOverlayProvider` handles the visual representation of dragged elements, and must wrap any part of your
1076+
application that uses drag and drop functionality:
1077+
1078+
```tsx
1079+
import { DragOverlayProvider, Overlay } from 'dragswag'
1080+
1081+
export default function App() {
1082+
return (
1083+
<DragOverlayProvider>
1084+
{/* Your draggable and droppable components */}
1085+
<div style={{ position: 'relative' }}>
1086+
<div style={{ position: 'absolute', top: 100, left: 100 }}>
1087+
<DraggableSquare color="red" />
1088+
</div>
1089+
<div style={{ position: 'absolute', top: 100, left: 300 }}>
1090+
<DroppableSquare color="green" />
1091+
</div>
1092+
</div>
1093+
</DragOverlayProvider>
1094+
)
1095+
}
1096+
```
1097+
1098+
The overlay can be customized with style and className props:
1099+
1100+
```tsx
1101+
<DragOverlayProvider
1102+
style={
1103+
{
1104+
/* your custom styles */
1105+
}
1106+
}
1107+
className="your-custom-class"
1108+
/>
1109+
```
1110+
10751111
## Author
10761112

10771113
Javier Aguilar

llms.txt

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,17 +176,17 @@ export const DroppableSquare = ({ color }: { color: string }) => {\
176176
----------------------------------------
177177

178178
TITLE: Rendering Draggable and Droppable Components in a React App
179-
DESCRIPTION: The main application component that renders instances of DraggableSquare and DroppableSquare. It uses basic CSS positioning to place them and includes the necessary Overlay component from dragswag to visualize the dragged element.
179+
DESCRIPTION: The main application component that renders instances of DraggableSquare and DroppableSquare. It uses basic CSS positioning to place them and includes the necessary overlay from dragswag to visualize the dragged element.
180180
SOURCE: https://github.com/pizzajsdev/dragswag/blob/main/README.md#_snippet_4
181181

182182
LANGUAGE: TSX
183183
CODE:
184184
```
185-
import { Overlay } from 'dragswag'\
185+
import { DragOverlayProvider } from 'dragswag'\
186186
\
187187
export default function App() {\
188188
return (\
189-
<>\
189+
<DragOverlayProvider>\
190190
{/* Render squares with absolute wrappers for positioning */}\
191191
<div style={{ position: 'relative' }}>\
192192
<div style={{ position: 'absolute', top: 100, left: 100 }}>\
@@ -196,9 +196,7 @@ export default function App() {\
196196
<DroppableSquare color=\"green\" />\
197197
</div>\
198198
</div>\
199-
{/* Render overlay to show the dragged component */}\
200-
<Overlay />\
201-
</>\
199+
</DragOverlayProvider>\
202200
)\
203201
}
204202
```
@@ -803,3 +801,47 @@ CODE:
803801
```
804802
bun add dragswag
805803
```
804+
805+
TITLE: Using the DragOverlayProvider
806+
DESCRIPTION: Shows how to properly set up the drag overlay system using the DragOverlayProvider component. The DragOverlayProvider must wrap any part of the application that uses drag and drop functionality, it also handles the visual representation of dragged elements.
807+
SOURCE: https://github.com/pizzajsdev/dragswag/blob/main/README.md#the-dragoverlayprovider
808+
809+
LANGUAGE: tsx
810+
CODE:
811+
```
812+
import { DragOverlayProvider } from 'dragswag'
813+
814+
export default function App() {
815+
return (
816+
<DragOverlayProvider>
817+
{/* Your draggable and droppable components */}
818+
<div>
819+
<DraggableSquare />
820+
<DroppableSquare />
821+
</div>
822+
</DragOverlayProvider>
823+
)
824+
}
825+
```
826+
827+
----------------------------------------
828+
829+
TITLE: Customizing the DragOverlayProvider Component
830+
DESCRIPTION: Demonstrates how to customize the appearance of the DragOverlayProvider component using style and className props.
831+
SOURCE: https://github.com/pizzajsdev/dragswag/blob/main/README.md#the-dragoverlayprovider
832+
833+
LANGUAGE: tsx
834+
CODE:
835+
```
836+
<DragOverlayProvider
837+
style={{
838+
zIndex: 1000,
839+
pointerEvents: 'none'
840+
}}
841+
className="custom-overlay"
842+
>
843+
{/* Your draggable and droppable components */}
844+
</DragOverlayProvider>
845+
```
846+
847+
----------------------------------------

0 commit comments

Comments
 (0)