( source: Element, target: Element, options?: DragAndDropOptions )
| 91 | * dispatching the full DragEvent lifecycle with synthetic events instead. |
| 92 | */ |
| 93 | export async function dragAndDrop( |
| 94 | source: Element, |
| 95 | target: Element, |
| 96 | options?: DragAndDropOptions |
| 97 | ): Promise<void> { |
| 98 | if (server.browser !== 'chromium') { |
| 99 | await userEvent.dragAndDrop(source, target, options); |
| 100 | return; |
| 101 | } |
| 102 | |
| 103 | let {dataTransfer, proxy} = createWritableDataTransfer(); |
| 104 | |
| 105 | let sourcePos = resolveClientPosition(source, options?.sourcePosition); |
| 106 | let targetPos = resolveClientPosition(target, options?.targetPosition); |
| 107 | |
| 108 | // Patch DragEvent.prototype.dataTransfer to return our proxy whenever the |
| 109 | // event carries our real dataTransfer. This is the only reliable way to make |
| 110 | // effectAllowed/dropEffect writable in Chromium for synthetic events. |
| 111 | let origDesc = Object.getOwnPropertyDescriptor(DragEvent.prototype, 'dataTransfer')!; |
| 112 | Object.defineProperty(DragEvent.prototype, 'dataTransfer', { |
| 113 | get() { |
| 114 | let real = origDesc.get!.call(this); |
| 115 | return real === dataTransfer ? proxy : real; |
| 116 | }, |
| 117 | configurable: true |
| 118 | }); |
| 119 | |
| 120 | try { |
| 121 | // Dispatch dragstart so useDrag populates the DataTransfer with items and sets effectAllowed. |
| 122 | source.dispatchEvent( |
| 123 | new DragEvent('dragstart', {dataTransfer, bubbles: true, cancelable: true, ...sourcePos}) |
| 124 | ); |
| 125 | |
| 126 | // Allow React state updates from the dragstart handler to flush. |
| 127 | await new Promise(resolve => requestAnimationFrame(resolve)); |
| 128 | |
| 129 | target.dispatchEvent( |
| 130 | new DragEvent('dragenter', {dataTransfer, bubbles: true, cancelable: true, ...targetPos}) |
| 131 | ); |
| 132 | target.dispatchEvent( |
| 133 | new DragEvent('dragover', {dataTransfer, bubbles: true, cancelable: true, ...targetPos}) |
| 134 | ); |
| 135 | |
| 136 | // In a real browser drag, the drop event only fires when dropEffect is not 'none'. |
| 137 | // useDrop sets dropEffect during dragenter/dragover based on getDropOperation. |
| 138 | if (proxy.dropEffect !== 'none') { |
| 139 | target.dispatchEvent( |
| 140 | new DragEvent('drop', {dataTransfer, bubbles: true, cancelable: true, ...targetPos}) |
| 141 | ); |
| 142 | } |
| 143 | |
| 144 | source.dispatchEvent( |
| 145 | new DragEvent('dragend', {dataTransfer, bubbles: true, cancelable: true, ...targetPos}) |
| 146 | ); |
| 147 | } finally { |
| 148 | // Restore the original dataTransfer getter. |
| 149 | Object.defineProperty(DragEvent.prototype, 'dataTransfer', origDesc); |
| 150 | } |
no test coverage detected