When dragging a text selection, the `event.target` in the `"dragstart"` event should be the `Text` node that the drag started from. > If it is a selection that is being dragged, then the source node is the `Text` node that the user started the drag on (typically the `Text` node that the user originally clicked). If the user did not specify a particular node, for example if the user just told the user agent to begin a drag of "the selection", then the source node is the first `Text` node containing a part of the selection. > → [Drag and drop spec](https://html.spec.whatwg.org/multipage/dnd.html#drag-and-drop-processing-model) `Chrome@121` and `Firefox@122` follow this behaviour. However, `Safari@17.2.1` does not. In Safari, the `event.target` when dragging a text selection, is being set to the `HTMLElement` that the drag started from. **Reproduction** ```js // reproduction case window.addEventListener("dragstart", (event) => { console.log("dragstart", { target: event.target, // true in Chrome and Firefox isTextNode: event.target instanceof Text, // true in Safari isHTMLElement: event.target instanceof HTMLElement }); }); ``` - [source code for example](https://codesandbox.io/s/text-selection-bug-for-safari-72spdd?file=/src/index.ts) - [standalone example](https://72spdd.csb.app/) (see `console.log` for results) **Workaround** ```js function findTextNode(event: DragEvent): Text | null { // Standard: the `event.target` should be the closest `Text` node. if (!isSafari()) { return event.target instanceof Text ? event.target : null; } // `event.target` should be the `Text` node that the drag started from // when dragging a selection. However, in Safari the closest `HTMLElement` is returned. // So we need to figure out if text is dragging ourselves. if (!(event.target instanceof HTMLElement)) { return null; } // unlikely that this particular drag is a text selection drag if (event.target.draggable) { return null; } // if the drag contains no text data, then not doing a text drag if (event.dataTransfer?.getData('text/plain') === '') { return null; } // Grab the first text node and use that const text: Text | undefined = Array.from(event.target.childNodes).find( (node): node is Text => node.nodeType === Node.TEXT_NODE, ); return text ?? null; } ```
For the workaround: ```diff - if (!(event.target instanceof HTMLElement)) { - return null; - } + // return `null` if there is no dataTransfer, or if `getData()` returns "" + if (!event.dataTransfer?.getData('text/plain')) { + return null; + } ```
*** WebKit ToT (274283@main) *** [Log] dragstart – {target: <p>, isTextNode: false, isHTMLElement: true} {target: <p>, isTextNode: false, isHTMLElement: true}ObjectisHTMLElement: trueisTextNode: falsetarget: <p><p>Object Prototype ___ As mentioned in Comment 0, it is indeed `false` for isTextNode while `true` for isHTMLElement. Although it is opposite in both cases for Chrome Canary 123 and matches with Firefox Nightly 124.
(In reply to Ahmad Saleem from comment #2) > *** WebKit ToT (274283@main) *** > > [Log] dragstart – {target: <p>, isTextNode: false, isHTMLElement: true} > {target: <p>, isTextNode: false, isHTMLElement: true}ObjectisHTMLElement: > trueisTextNode: falsetarget: <p><p>Object Prototype > > ___ > > As mentioned in Comment 0, it is indeed `false` for isTextNode while `true` > for isHTMLElement. Although it is opposite in both cases for Chrome Canary > 123 and matches with Firefox Nightly 124. Chrome matches with Firefox (just to be clear) while Safari / WebKit is outlier. Hence, I added `BrowserCompat` tag as well.
<rdar://problem/122574058>
Following library has workaround for this - https://github.com/atlassian/pragmatic-drag-and-drop