New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@atlaskit/drag-and-drop

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@atlaskit/drag-and-drop - npm Package Compare versions

Comparing version 0.13.0 to 0.14.0

11

CHANGELOG.md
# @atlaskit/drag-and-drop
## 0.14.0
### Minor Changes
- [`eab6d26451d`](https://bitbucket.org/atlassian/atlassian-frontend/commits/eab6d26451d) - Improving the resilience of our workaround for a [Browser bug](https://bugs.chromium.org/p/chromium/issues/detail?id=410328) where after a drag finishes, an unrelated element can be entered into.
- [`ba7ea570aee`](https://bitbucket.org/atlassian/atlassian-frontend/commits/ba7ea570aee) - > Both of these changes should not impact most consumers as they are targeted at edge cases.
- **Fix**: We no longer extract user input (eg `clientX`) from native `"dragleave"` events due to a [Bug with Chrome we discovered](https://bugs.chromium.org/p/chromium/issues/detail?id=1429937). Due to this bug, it was possible for `location.current.input` to be incorrectly set in `onDropTargetChange` and `onDrop` when a user was cancelling a drag or dropping or no drop targets.
- **Fix**: `location.previous.dropTargets` _should_ always point to the `location.current.dropTargets` value from the previous event (exception: `onGenerateDragPreview` and `onDragStart` have the same `location.previous` and `location.current` values). Previously, the `location.previous.dropTargets` value did not match the last events `location.current.dropTargets` value in `onDrop`. `onDrop()` would incorrectly use the `location.current` and `location.previous` values from the last event rather than creating a new `location.current` entry. Now, `onDrop()`, `location.previous.dropTargets` points to the `location.current.dropTargets` from the last event (same as all other events) and `location.current.dropTargets` points to what the previous drop target was as well (no change)
## 0.13.0

@@ -4,0 +15,0 @@

55

dist/cjs/ledger/dispatch-consumer-event.js

@@ -38,8 +38,26 @@ "use strict";

var source = _ref.source,
initial = _ref.initial,
dispatchEvent = _ref.dispatchEvent;
var previous = {
dropTargets: []
};
function safeDispatch(args) {
dispatchEvent(args);
previous = {
dropTargets: args.payload.location.current.dropTargets
};
}
var dispatch = {
start: function start(_ref2) {
var location = _ref2.location,
nativeSetDragImage = _ref2.nativeSetDragImage;
dispatchEvent({
var nativeSetDragImage = _ref2.nativeSetDragImage;
// Ensuring that both `onGenerateDragPreview` and `onDragStart` get the same location.
// We do this so that `previous` is`[]` in `onDragStart` (which is logical)
var location = {
current: initial,
previous: previous,
initial: initial
};
// a `onGenerateDragPreview` does _not_ add another entry for `previous`
// onDragPreview
safeDispatch({
eventName: 'onGenerateDragPreview',

@@ -53,3 +71,3 @@ payload: {

dragStart.schedule(function () {
dispatchEvent({
safeDispatch({
eventName: 'onDragStart',

@@ -64,10 +82,14 @@ payload: {

dragUpdate: function dragUpdate(_ref3) {
var location = _ref3.location;
var current = _ref3.current;
dragStart.flush();
scheduleOnDrag.cancel();
dispatchEvent({
safeDispatch({
eventName: 'onDropTargetChange',
payload: {
source: source,
location: location
location: {
initial: initial,
previous: previous,
current: current
}
}

@@ -77,6 +99,11 @@ });

drag: function drag(_ref4) {
var location = _ref4.location;
var current = _ref4.current;
scheduleOnDrag(function () {
dragStart.flush();
dispatchEvent({
var location = {
initial: initial,
previous: previous,
current: current
};
safeDispatch({
eventName: 'onDrag',

@@ -91,11 +118,15 @@ payload: {

drop: function drop(_ref5) {
var location = _ref5.location,
var current = _ref5.current,
updatedSourcePayload = _ref5.updatedExternalPayload;
dragStart.flush();
scheduleOnDrag.cancel();
dispatchEvent({
safeDispatch({
eventName: 'onDrop',
payload: {
source: updatedSourcePayload !== null && updatedSourcePayload !== void 0 ? updatedSourcePayload : source,
location: location
location: {
current: current,
previous: previous,
initial: initial
}
}

@@ -102,0 +133,0 @@ });

@@ -53,28 +53,12 @@ "use strict";

});
var state = {
initial: initial,
current: initial,
// no 'previous' when we first start dragging
previous: {
dropTargets: []
}
};
function updateState(_ref3) {
var next = _ref3.next;
var newState = {
initial: initial,
previous: {
dropTargets: state.current.dropTargets
},
current: next
};
state = newState;
}
var current = initial;
// Setting initial drop effect for the drag
setDropEffect({
event: event,
current: state.initial.dropTargets
current: initial.dropTargets
});
var dispatch = (0, _dispatchConsumerEvent.makeDispatch)({
source: dragInterface.payload,
dispatchEvent: dispatchEvent
dispatchEvent: dispatchEvent,
initial: initial
});

@@ -84,3 +68,3 @@ function updateDropTargets(next) {

var hasChanged = hasHierarchyChanged({
current: state.current.dropTargets,
current: current.dropTargets,
next: next.dropTargets

@@ -92,8 +76,6 @@ });

// Consumers can get the latest data by using `onDrag`
updateState({
next: next
});
current = next;
if (hasChanged) {
dispatch.dragUpdate({
location: state
current: current
});

@@ -108,3 +90,3 @@ }

source: dragInterface.payload,
current: state.current.dropTargets
current: current.dropTargets
});

@@ -124,25 +106,25 @@ if (nextDropTargets.length) {

}
function onDrop(_ref3) {
var updatedExternalPayload = _ref3.updatedExternalPayload;
dispatch.drop({
current: current,
updatedExternalPayload: updatedExternalPayload
});
}
function cancel() {
// The spec behaviour is that when a drop is cancelled,
// a 'dragleave' event is fired on the active drop target
// before a `dragend` event.
// We are replicating that behaviour here
// The spec behaviour is that when a drag is cancelled, or when dropping on no drop targets,
// a "dragleave" event is fired on the active drop target before a "dragend" event.
// We are replicating that behaviour in `cancel` if there are any active drop targets to
// ensure consistent behaviour.
//
// Note: When cancelling, or dropping on no drop targets, a "dragleave" event
// will have already cleared the dropTargets to `[]` (as that particular "dragleave" has a `relatedTarget` of `null`)
// If there are any active drop targets we will create
// and update event to leave them
if (state.current.dropTargets.length) {
updateState({
next: {
// clear the drop targets
dropTargets: [],
// keep the same input
input: state.current.input
}
if (current.dropTargets.length) {
updateDropTargets({
dropTargets: [],
input: current.input
});
dispatch.dragUpdate({
location: state
});
}
dispatch.drop({
location: state,
onDrop({
updatedExternalPayload: null

@@ -176,4 +158,5 @@ });

// 2. let consumers know a move has occurred
// This will include the latest 'input' values
dispatch.drag({
location: state
current: current
});

@@ -187,5 +170,7 @@ }

// when the user is leaving the `window`.
// When we leave the `window` we want to clear any active drop targets,
// Internal drags: when we leave the `window` we want to clear any active drop targets,
// but the drag is not yet over. The user could drag back into the window.
// We only need to do this because of stickiness
// External drags: when we leave the `window` the drag operation is over,
// we will start another drag operation
type: 'dragleave',

@@ -198,4 +183,17 @@ listener: function listener(event) {

}
// When a drag is ending without a drop target (or when the drag is cancelled),
// All browsers fire:
// 1. "drag"
// 2. "dragleave"
// These events have `event.relatedTarget == null` so this code path is also hit in those cases.
// This is all good! We would be clearing the dropTargets in `cancel()` after the "dragend"
// 🐛 Bug workaround: intentionally not updating `input` in "dragleave"
// In Chrome, this final "dragleave" has default input values (eg clientX == 0)
// rather than the users current input values
//
// - [Conversation](https://twitter.com/alexandereardon/status/1642697633864241152)
// - [Bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1429937)
updateDropTargets({
input: (0, _getInput.getInput)(event),
input: current.input,
dropTargets: []

@@ -211,8 +209,4 @@ });

var _dragInterface$getDro;
// this can only happen if the browser allowed the drop
// A "drop" can only happen if the browser allowed the drop
if (dragInterface.startedFrom === 'external') {
dragInterface.key;
}
// Opting out of standard browser drop behaviour for the drag

@@ -224,6 +218,5 @@ event.preventDefault();

event: event,
current: state.current.dropTargets
current: current.dropTargets
});
dispatch.drop({
location: state,
onDrop({
updatedExternalPayload: dragInterface.startedFrom === 'external' ? ((_dragInterface$getDro = dragInterface.getDropPayload) === null || _dragInterface$getDro === void 0 ? void 0 : _dragInterface$getDro.call(dragInterface, event)) || null : null

@@ -236,3 +229,5 @@ });

if (dragInterface.startedFrom === 'internal') {
(0, _fixPostDragPointerBug.fixPostDragPointerBug)(state);
(0, _fixPostDragPointerBug.fixPostDragPointerBug)({
current: current
});
}

@@ -253,3 +248,5 @@ }

if (dragInterface.startedFrom === 'internal') {
(0, _fixPostDragPointerBug.fixPostDragPointerBug)(state);
(0, _fixPostDragPointerBug.fixPostDragPointerBug)({
current: current
});
}

@@ -309,3 +306,2 @@ }

dispatch.start({
location: state,
nativeSetDragImage: getNativeSetDragImage(event)

@@ -312,0 +308,0 @@ });

"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {

@@ -8,5 +7,3 @@ value: true

exports.fixPostDragPointerBug = fixPostDragPointerBug;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
var _bindEventListener = require("bind-event-listener");
var _combine = require("./combine");
/** Set a `style` property on a `HTMLElement`

@@ -18,10 +15,12 @@ *

var property = _ref.property,
rule = _ref.rule;
var original = el.style[property];
el.style[property] = rule;
rule = _ref.rule,
_ref$priority = _ref.priority,
priority = _ref$priority === void 0 ? '' : _ref$priority;
var originalValue = el.style.getPropertyValue(property);
var originalPriority = el.style.getPropertyPriority(property);
el.style.setProperty(property, rule, priority);
return function cleanup() {
el.style[property] = original;
el.style.setProperty(property, originalValue, originalPriority);
};
}
function noop() {}

@@ -40,26 +39,33 @@ /**

*/
function fixUnderPointer(location) {
var underUsersPointer = document.elementFromPoint(location.current.input.clientX, location.current.input.clientY);
function fixUnderPointer(_ref2) {
var current = _ref2.current;
var underUsersPointer = document.elementFromPoint(current.input.clientX, current.input.clientY);
if (!(underUsersPointer instanceof HTMLElement)) {
return noop;
return null;
}
// Disabling pointer events on all first level children
// Equivalent of '> * { pointer-events: none; }', except we don't need
// to insert / remove a style rule
var unsetChildren = Array.from(underUsersPointer.children).map(function (child) {
if (!(child instanceof HTMLElement)) {
return noop;
}
return setStyle(child, {
property: 'pointerEvents',
rule: 'none'
});
// Debug note: change from 'pointer-events: none' to 'background: green'
// to get a better sense of what is being achieved
return setStyle(underUsersPointer, {
property: 'pointer-events',
rule: 'auto',
priority: 'important'
});
return _combine.combine.apply(void 0, [setStyle(underUsersPointer, {
property: 'pointerEvents',
rule: 'auto'
})].concat((0, _toConsumableArray2.default)(unsetChildren)));
}
function blockPointerEventsOnEverything() {
var _element$sheet;
var element = document.createElement('style');
// Adding a data attribute so to make it super clear to consumers
// (and to our tests) what this temporary style tag is for
element.setAttribute('pdnd-post-drag-fix', 'true');
document.head.appendChild(element);
// Debug note: change from 'pointer-events: none' to 'background: red'
// to get a better sense of what is being achieved
(_element$sheet = element.sheet) === null || _element$sheet === void 0 ? void 0 : _element$sheet.insertRule('* { pointer-events: none !important; }');
return function cleanup() {
document.head.removeChild(element);
};
}
/** 🔥🤮 Fix (Chrome, Safari and Firefox) bug where the element under where the user started dragging

@@ -72,13 +78,7 @@ * (on the viewport) is entered into by the browser after a drag finishes ("drop" or "dragend")

*
* Conceptually this is what we are doing (but without adding any new style rules to the page)
* ```css
* body { pointer-events: none; }
* elementUnderPointer { pointer-events: auto; }
* elementUnderPointer > * { pointer-events: none; }
* ```
*
* - [Visual explanation of bug](https://twitter.com/alexandereardon/status/1633614212873465856)
* - [Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=410328)
*/
function fixPostDragPointerBug(location) {
function fixPostDragPointerBug(_ref3) {
var current = _ref3.current;
// Queuing a microtask to give any opportunity for frameworks to update their UI in a microtask

@@ -89,16 +89,12 @@ // Note: react@18 does standard state updates in a microtask

queueMicrotask(function () {
// we need to get `atDestination` before turning off pointer events on the body,
// or `document.elementFromPoint` will just return the `html` element (`document.documentElement`)
// We are allowing pointer events on the element under the users cursor,
// but _not_ on children of that element
var unsetUnderPointer = fixUnderPointer(location);
var unsetBodyStyles = setStyle(document.body, {
property: 'pointerEvents',
rule: 'none'
// Applying fix to element under the pointer before disabling pointer events everywhere,
// Otherwise `element.elementFromPoint` would return `<html>` (document.documentElement)
var undoUnderPointer = fixUnderPointer({
current: current
});
var undoGlobalBlock = blockPointerEventsOnEverything();
function cleanup() {
unbindEvents();
unsetBodyStyles();
unsetUnderPointer();
undoUnderPointer === null || undoUnderPointer === void 0 ? void 0 : undoUnderPointer();
undoGlobalBlock();
}

@@ -105,0 +101,0 @@ var unbindEvents = (0, _bindEventListener.bindAll)(window, [{

{
"name": "@atlaskit/drag-and-drop",
"version": "0.13.0",
"version": "0.14.0",
"sideEffects": false
}

@@ -29,10 +29,28 @@ import rafSchd from 'raf-schd';

source,
initial,
dispatchEvent
}) {
let previous = {
dropTargets: []
};
function safeDispatch(args) {
dispatchEvent(args);
previous = {
dropTargets: args.payload.location.current.dropTargets
};
}
const dispatch = {
start({
location,
nativeSetDragImage
}) {
dispatchEvent({
// Ensuring that both `onGenerateDragPreview` and `onDragStart` get the same location.
// We do this so that `previous` is`[]` in `onDragStart` (which is logical)
const location = {
current: initial,
previous,
initial
};
// a `onGenerateDragPreview` does _not_ add another entry for `previous`
// onDragPreview
safeDispatch({
eventName: 'onGenerateDragPreview',

@@ -46,3 +64,3 @@ payload: {

dragStart.schedule(() => {
dispatchEvent({
safeDispatch({
eventName: 'onDragStart',

@@ -57,11 +75,15 @@ payload: {

dragUpdate({
location
current
}) {
dragStart.flush();
scheduleOnDrag.cancel();
dispatchEvent({
safeDispatch({
eventName: 'onDropTargetChange',
payload: {
source,
location
location: {
initial,
previous,
current
}
}

@@ -71,7 +93,12 @@ });

drag({
location
current
}) {
scheduleOnDrag(() => {
dragStart.flush();
dispatchEvent({
const location = {
initial,
previous,
current
};
safeDispatch({
eventName: 'onDrag',

@@ -86,3 +113,3 @@ payload: {

drop({
location,
current,
updatedExternalPayload: updatedSourcePayload

@@ -92,7 +119,11 @@ }) {

scheduleOnDrag.cancel();
dispatchEvent({
safeDispatch({
eventName: 'onDrop',
payload: {
source: updatedSourcePayload !== null && updatedSourcePayload !== void 0 ? updatedSourcePayload : source,
location
location: {
current,
previous,
initial
}
}

@@ -99,0 +130,0 @@ });

@@ -49,29 +49,12 @@ import { bindAll } from 'bind-event-listener';

});
let state = {
initial,
current: initial,
// no 'previous' when we first start dragging
previous: {
dropTargets: []
}
};
function updateState({
next
}) {
const newState = {
initial,
previous: {
dropTargets: state.current.dropTargets
},
current: next
};
state = newState;
}
let current = initial;
// Setting initial drop effect for the drag
setDropEffect({
event,
current: state.initial.dropTargets
current: initial.dropTargets
});
const dispatch = makeDispatch({
source: dragInterface.payload,
dispatchEvent
dispatchEvent,
initial
});

@@ -81,3 +64,3 @@ function updateDropTargets(next) {

const hasChanged = hasHierarchyChanged({
current: state.current.dropTargets,
current: current.dropTargets,
next: next.dropTargets

@@ -89,8 +72,6 @@ });

// Consumers can get the latest data by using `onDrag`
updateState({
next
});
current = next;
if (hasChanged) {
dispatch.dragUpdate({
location: state
current
});

@@ -105,3 +86,3 @@ }

source: dragInterface.payload,
current: state.current.dropTargets
current: current.dropTargets
});

@@ -121,25 +102,26 @@ if (nextDropTargets.length) {

}
function onDrop({
updatedExternalPayload
}) {
dispatch.drop({
current,
updatedExternalPayload
});
}
function cancel() {
// The spec behaviour is that when a drop is cancelled,
// a 'dragleave' event is fired on the active drop target
// before a `dragend` event.
// We are replicating that behaviour here
// The spec behaviour is that when a drag is cancelled, or when dropping on no drop targets,
// a "dragleave" event is fired on the active drop target before a "dragend" event.
// We are replicating that behaviour in `cancel` if there are any active drop targets to
// ensure consistent behaviour.
//
// Note: When cancelling, or dropping on no drop targets, a "dragleave" event
// will have already cleared the dropTargets to `[]` (as that particular "dragleave" has a `relatedTarget` of `null`)
// If there are any active drop targets we will create
// and update event to leave them
if (state.current.dropTargets.length) {
updateState({
next: {
// clear the drop targets
dropTargets: [],
// keep the same input
input: state.current.input
}
if (current.dropTargets.length) {
updateDropTargets({
dropTargets: [],
input: current.input
});
dispatch.dragUpdate({
location: state
});
}
dispatch.drop({
location: state,
onDrop({
updatedExternalPayload: null

@@ -173,4 +155,5 @@ });

// 2. let consumers know a move has occurred
// This will include the latest 'input' values
dispatch.drag({
location: state
current
});

@@ -184,5 +167,7 @@ }

// when the user is leaving the `window`.
// When we leave the `window` we want to clear any active drop targets,
// Internal drags: when we leave the `window` we want to clear any active drop targets,
// but the drag is not yet over. The user could drag back into the window.
// We only need to do this because of stickiness
// External drags: when we leave the `window` the drag operation is over,
// we will start another drag operation
type: 'dragleave',

@@ -195,4 +180,17 @@ listener(event) {

}
// When a drag is ending without a drop target (or when the drag is cancelled),
// All browsers fire:
// 1. "drag"
// 2. "dragleave"
// These events have `event.relatedTarget == null` so this code path is also hit in those cases.
// This is all good! We would be clearing the dropTargets in `cancel()` after the "dragend"
// 🐛 Bug workaround: intentionally not updating `input` in "dragleave"
// In Chrome, this final "dragleave" has default input values (eg clientX == 0)
// rather than the users current input values
//
// - [Conversation](https://twitter.com/alexandereardon/status/1642697633864241152)
// - [Bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1429937)
updateDropTargets({
input: getInput(event),
input: current.input,
dropTargets: []

@@ -208,8 +206,4 @@ });

var _dragInterface$getDro;
// this can only happen if the browser allowed the drop
// A "drop" can only happen if the browser allowed the drop
if (dragInterface.startedFrom === 'external') {
dragInterface.key;
}
// Opting out of standard browser drop behaviour for the drag

@@ -221,6 +215,5 @@ event.preventDefault();

event,
current: state.current.dropTargets
current: current.dropTargets
});
dispatch.drop({
location: state,
onDrop({
updatedExternalPayload: dragInterface.startedFrom === 'external' ? ((_dragInterface$getDro = dragInterface.getDropPayload) === null || _dragInterface$getDro === void 0 ? void 0 : _dragInterface$getDro.call(dragInterface, event)) || null : null

@@ -233,3 +226,5 @@ });

if (dragInterface.startedFrom === 'internal') {
fixPostDragPointerBug(state);
fixPostDragPointerBug({
current
});
}

@@ -250,3 +245,5 @@ }

if (dragInterface.startedFrom === 'internal') {
fixPostDragPointerBug(state);
fixPostDragPointerBug({
current
});
}

@@ -306,3 +303,2 @@ }

dispatch.start({
location: state,
nativeSetDragImage: getNativeSetDragImage(event)

@@ -309,0 +305,0 @@ });

import { bindAll } from 'bind-event-listener';
import { combine } from './combine';
/** Set a `style` property on a `HTMLElement`

@@ -9,11 +8,12 @@ *

property,
rule
rule,
priority = ''
}) {
const original = el.style[property];
el.style[property] = rule;
const originalValue = el.style.getPropertyValue(property);
const originalPriority = el.style.getPropertyPriority(property);
el.style.setProperty(property, rule, priority);
return function cleanup() {
el.style[property] = original;
el.style.setProperty(property, originalValue, originalPriority);
};
}
function noop() {}

@@ -32,26 +32,34 @@ /**

*/
function fixUnderPointer(location) {
const underUsersPointer = document.elementFromPoint(location.current.input.clientX, location.current.input.clientY);
function fixUnderPointer({
current
}) {
const underUsersPointer = document.elementFromPoint(current.input.clientX, current.input.clientY);
if (!(underUsersPointer instanceof HTMLElement)) {
return noop;
return null;
}
// Disabling pointer events on all first level children
// Equivalent of '> * { pointer-events: none; }', except we don't need
// to insert / remove a style rule
const unsetChildren = Array.from(underUsersPointer.children).map(child => {
if (!(child instanceof HTMLElement)) {
return noop;
}
return setStyle(child, {
property: 'pointerEvents',
rule: 'none'
});
// Debug note: change from 'pointer-events: none' to 'background: green'
// to get a better sense of what is being achieved
return setStyle(underUsersPointer, {
property: 'pointer-events',
rule: 'auto',
priority: 'important'
});
return combine(setStyle(underUsersPointer, {
property: 'pointerEvents',
rule: 'auto'
}), ...unsetChildren);
}
function blockPointerEventsOnEverything() {
var _element$sheet;
const element = document.createElement('style');
// Adding a data attribute so to make it super clear to consumers
// (and to our tests) what this temporary style tag is for
element.setAttribute('pdnd-post-drag-fix', 'true');
document.head.appendChild(element);
// Debug note: change from 'pointer-events: none' to 'background: red'
// to get a better sense of what is being achieved
(_element$sheet = element.sheet) === null || _element$sheet === void 0 ? void 0 : _element$sheet.insertRule('* { pointer-events: none !important; }');
return function cleanup() {
document.head.removeChild(element);
};
}
/** 🔥🤮 Fix (Chrome, Safari and Firefox) bug where the element under where the user started dragging

@@ -64,13 +72,8 @@ * (on the viewport) is entered into by the browser after a drag finishes ("drop" or "dragend")

*
* Conceptually this is what we are doing (but without adding any new style rules to the page)
* ```css
* body { pointer-events: none; }
* elementUnderPointer { pointer-events: auto; }
* elementUnderPointer > * { pointer-events: none; }
* ```
*
* - [Visual explanation of bug](https://twitter.com/alexandereardon/status/1633614212873465856)
* - [Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=410328)
*/
export function fixPostDragPointerBug(location) {
export function fixPostDragPointerBug({
current
}) {
// Queuing a microtask to give any opportunity for frameworks to update their UI in a microtask

@@ -81,16 +84,12 @@ // Note: react@18 does standard state updates in a microtask

queueMicrotask(() => {
// we need to get `atDestination` before turning off pointer events on the body,
// or `document.elementFromPoint` will just return the `html` element (`document.documentElement`)
// We are allowing pointer events on the element under the users cursor,
// but _not_ on children of that element
const unsetUnderPointer = fixUnderPointer(location);
const unsetBodyStyles = setStyle(document.body, {
property: 'pointerEvents',
rule: 'none'
// Applying fix to element under the pointer before disabling pointer events everywhere,
// Otherwise `element.elementFromPoint` would return `<html>` (document.documentElement)
const undoUnderPointer = fixUnderPointer({
current
});
const undoGlobalBlock = blockPointerEventsOnEverything();
function cleanup() {
unbindEvents();
unsetBodyStyles();
unsetUnderPointer();
undoUnderPointer === null || undoUnderPointer === void 0 ? void 0 : undoUnderPointer();
undoGlobalBlock();
}

@@ -97,0 +96,0 @@ const unbindEvents = bindAll(window, [{

{
"name": "@atlaskit/drag-and-drop",
"version": "0.13.0",
"version": "0.14.0",
"sideEffects": false
}

@@ -31,8 +31,26 @@ import rafSchd from 'raf-schd';

var source = _ref.source,
initial = _ref.initial,
dispatchEvent = _ref.dispatchEvent;
var previous = {
dropTargets: []
};
function safeDispatch(args) {
dispatchEvent(args);
previous = {
dropTargets: args.payload.location.current.dropTargets
};
}
var dispatch = {
start: function start(_ref2) {
var location = _ref2.location,
nativeSetDragImage = _ref2.nativeSetDragImage;
dispatchEvent({
var nativeSetDragImage = _ref2.nativeSetDragImage;
// Ensuring that both `onGenerateDragPreview` and `onDragStart` get the same location.
// We do this so that `previous` is`[]` in `onDragStart` (which is logical)
var location = {
current: initial,
previous: previous,
initial: initial
};
// a `onGenerateDragPreview` does _not_ add another entry for `previous`
// onDragPreview
safeDispatch({
eventName: 'onGenerateDragPreview',

@@ -46,3 +64,3 @@ payload: {

dragStart.schedule(function () {
dispatchEvent({
safeDispatch({
eventName: 'onDragStart',

@@ -57,10 +75,14 @@ payload: {

dragUpdate: function dragUpdate(_ref3) {
var location = _ref3.location;
var current = _ref3.current;
dragStart.flush();
scheduleOnDrag.cancel();
dispatchEvent({
safeDispatch({
eventName: 'onDropTargetChange',
payload: {
source: source,
location: location
location: {
initial: initial,
previous: previous,
current: current
}
}

@@ -70,6 +92,11 @@ });

drag: function drag(_ref4) {
var location = _ref4.location;
var current = _ref4.current;
scheduleOnDrag(function () {
dragStart.flush();
dispatchEvent({
var location = {
initial: initial,
previous: previous,
current: current
};
safeDispatch({
eventName: 'onDrag',

@@ -84,11 +111,15 @@ payload: {

drop: function drop(_ref5) {
var location = _ref5.location,
var current = _ref5.current,
updatedSourcePayload = _ref5.updatedExternalPayload;
dragStart.flush();
scheduleOnDrag.cancel();
dispatchEvent({
safeDispatch({
eventName: 'onDrop',
payload: {
source: updatedSourcePayload !== null && updatedSourcePayload !== void 0 ? updatedSourcePayload : source,
location: location
location: {
current: current,
previous: previous,
initial: initial
}
}

@@ -95,0 +126,0 @@ });

@@ -47,28 +47,12 @@ import { bindAll } from 'bind-event-listener';

});
var state = {
initial: initial,
current: initial,
// no 'previous' when we first start dragging
previous: {
dropTargets: []
}
};
function updateState(_ref3) {
var next = _ref3.next;
var newState = {
initial: initial,
previous: {
dropTargets: state.current.dropTargets
},
current: next
};
state = newState;
}
var current = initial;
// Setting initial drop effect for the drag
setDropEffect({
event: event,
current: state.initial.dropTargets
current: initial.dropTargets
});
var dispatch = makeDispatch({
source: dragInterface.payload,
dispatchEvent: dispatchEvent
dispatchEvent: dispatchEvent,
initial: initial
});

@@ -78,3 +62,3 @@ function updateDropTargets(next) {

var hasChanged = hasHierarchyChanged({
current: state.current.dropTargets,
current: current.dropTargets,
next: next.dropTargets

@@ -86,8 +70,6 @@ });

// Consumers can get the latest data by using `onDrag`
updateState({
next: next
});
current = next;
if (hasChanged) {
dispatch.dragUpdate({
location: state
current: current
});

@@ -102,3 +84,3 @@ }

source: dragInterface.payload,
current: state.current.dropTargets
current: current.dropTargets
});

@@ -118,25 +100,25 @@ if (nextDropTargets.length) {

}
function onDrop(_ref3) {
var updatedExternalPayload = _ref3.updatedExternalPayload;
dispatch.drop({
current: current,
updatedExternalPayload: updatedExternalPayload
});
}
function cancel() {
// The spec behaviour is that when a drop is cancelled,
// a 'dragleave' event is fired on the active drop target
// before a `dragend` event.
// We are replicating that behaviour here
// The spec behaviour is that when a drag is cancelled, or when dropping on no drop targets,
// a "dragleave" event is fired on the active drop target before a "dragend" event.
// We are replicating that behaviour in `cancel` if there are any active drop targets to
// ensure consistent behaviour.
//
// Note: When cancelling, or dropping on no drop targets, a "dragleave" event
// will have already cleared the dropTargets to `[]` (as that particular "dragleave" has a `relatedTarget` of `null`)
// If there are any active drop targets we will create
// and update event to leave them
if (state.current.dropTargets.length) {
updateState({
next: {
// clear the drop targets
dropTargets: [],
// keep the same input
input: state.current.input
}
if (current.dropTargets.length) {
updateDropTargets({
dropTargets: [],
input: current.input
});
dispatch.dragUpdate({
location: state
});
}
dispatch.drop({
location: state,
onDrop({
updatedExternalPayload: null

@@ -170,4 +152,5 @@ });

// 2. let consumers know a move has occurred
// This will include the latest 'input' values
dispatch.drag({
location: state
current: current
});

@@ -181,5 +164,7 @@ }

// when the user is leaving the `window`.
// When we leave the `window` we want to clear any active drop targets,
// Internal drags: when we leave the `window` we want to clear any active drop targets,
// but the drag is not yet over. The user could drag back into the window.
// We only need to do this because of stickiness
// External drags: when we leave the `window` the drag operation is over,
// we will start another drag operation
type: 'dragleave',

@@ -192,4 +177,17 @@ listener: function listener(event) {

}
// When a drag is ending without a drop target (or when the drag is cancelled),
// All browsers fire:
// 1. "drag"
// 2. "dragleave"
// These events have `event.relatedTarget == null` so this code path is also hit in those cases.
// This is all good! We would be clearing the dropTargets in `cancel()` after the "dragend"
// 🐛 Bug workaround: intentionally not updating `input` in "dragleave"
// In Chrome, this final "dragleave" has default input values (eg clientX == 0)
// rather than the users current input values
//
// - [Conversation](https://twitter.com/alexandereardon/status/1642697633864241152)
// - [Bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1429937)
updateDropTargets({
input: getInput(event),
input: current.input,
dropTargets: []

@@ -205,8 +203,4 @@ });

var _dragInterface$getDro;
// this can only happen if the browser allowed the drop
// A "drop" can only happen if the browser allowed the drop
if (dragInterface.startedFrom === 'external') {
dragInterface.key;
}
// Opting out of standard browser drop behaviour for the drag

@@ -218,6 +212,5 @@ event.preventDefault();

event: event,
current: state.current.dropTargets
current: current.dropTargets
});
dispatch.drop({
location: state,
onDrop({
updatedExternalPayload: dragInterface.startedFrom === 'external' ? ((_dragInterface$getDro = dragInterface.getDropPayload) === null || _dragInterface$getDro === void 0 ? void 0 : _dragInterface$getDro.call(dragInterface, event)) || null : null

@@ -230,3 +223,5 @@ });

if (dragInterface.startedFrom === 'internal') {
fixPostDragPointerBug(state);
fixPostDragPointerBug({
current: current
});
}

@@ -247,3 +242,5 @@ }

if (dragInterface.startedFrom === 'internal') {
fixPostDragPointerBug(state);
fixPostDragPointerBug({
current: current
});
}

@@ -303,3 +300,2 @@ }

dispatch.start({
location: state,
nativeSetDragImage: getNativeSetDragImage(event)

@@ -306,0 +302,0 @@ });

@@ -1,4 +0,2 @@

import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import { bindAll } from 'bind-event-listener';
import { combine } from './combine';
/** Set a `style` property on a `HTMLElement`

@@ -10,10 +8,12 @@ *

var property = _ref.property,
rule = _ref.rule;
var original = el.style[property];
el.style[property] = rule;
rule = _ref.rule,
_ref$priority = _ref.priority,
priority = _ref$priority === void 0 ? '' : _ref$priority;
var originalValue = el.style.getPropertyValue(property);
var originalPriority = el.style.getPropertyPriority(property);
el.style.setProperty(property, rule, priority);
return function cleanup() {
el.style[property] = original;
el.style.setProperty(property, originalValue, originalPriority);
};
}
function noop() {}

@@ -32,26 +32,33 @@ /**

*/
function fixUnderPointer(location) {
var underUsersPointer = document.elementFromPoint(location.current.input.clientX, location.current.input.clientY);
function fixUnderPointer(_ref2) {
var current = _ref2.current;
var underUsersPointer = document.elementFromPoint(current.input.clientX, current.input.clientY);
if (!(underUsersPointer instanceof HTMLElement)) {
return noop;
return null;
}
// Disabling pointer events on all first level children
// Equivalent of '> * { pointer-events: none; }', except we don't need
// to insert / remove a style rule
var unsetChildren = Array.from(underUsersPointer.children).map(function (child) {
if (!(child instanceof HTMLElement)) {
return noop;
}
return setStyle(child, {
property: 'pointerEvents',
rule: 'none'
});
// Debug note: change from 'pointer-events: none' to 'background: green'
// to get a better sense of what is being achieved
return setStyle(underUsersPointer, {
property: 'pointer-events',
rule: 'auto',
priority: 'important'
});
return combine.apply(void 0, [setStyle(underUsersPointer, {
property: 'pointerEvents',
rule: 'auto'
})].concat(_toConsumableArray(unsetChildren)));
}
function blockPointerEventsOnEverything() {
var _element$sheet;
var element = document.createElement('style');
// Adding a data attribute so to make it super clear to consumers
// (and to our tests) what this temporary style tag is for
element.setAttribute('pdnd-post-drag-fix', 'true');
document.head.appendChild(element);
// Debug note: change from 'pointer-events: none' to 'background: red'
// to get a better sense of what is being achieved
(_element$sheet = element.sheet) === null || _element$sheet === void 0 ? void 0 : _element$sheet.insertRule('* { pointer-events: none !important; }');
return function cleanup() {
document.head.removeChild(element);
};
}
/** 🔥🤮 Fix (Chrome, Safari and Firefox) bug where the element under where the user started dragging

@@ -64,13 +71,7 @@ * (on the viewport) is entered into by the browser after a drag finishes ("drop" or "dragend")

*
* Conceptually this is what we are doing (but without adding any new style rules to the page)
* ```css
* body { pointer-events: none; }
* elementUnderPointer { pointer-events: auto; }
* elementUnderPointer > * { pointer-events: none; }
* ```
*
* - [Visual explanation of bug](https://twitter.com/alexandereardon/status/1633614212873465856)
* - [Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=410328)
*/
export function fixPostDragPointerBug(location) {
export function fixPostDragPointerBug(_ref3) {
var current = _ref3.current;
// Queuing a microtask to give any opportunity for frameworks to update their UI in a microtask

@@ -81,16 +82,12 @@ // Note: react@18 does standard state updates in a microtask

queueMicrotask(function () {
// we need to get `atDestination` before turning off pointer events on the body,
// or `document.elementFromPoint` will just return the `html` element (`document.documentElement`)
// We are allowing pointer events on the element under the users cursor,
// but _not_ on children of that element
var unsetUnderPointer = fixUnderPointer(location);
var unsetBodyStyles = setStyle(document.body, {
property: 'pointerEvents',
rule: 'none'
// Applying fix to element under the pointer before disabling pointer events everywhere,
// Otherwise `element.elementFromPoint` would return `<html>` (document.documentElement)
var undoUnderPointer = fixUnderPointer({
current: current
});
var undoGlobalBlock = blockPointerEventsOnEverything();
function cleanup() {
unbindEvents();
unsetBodyStyles();
unsetUnderPointer();
undoUnderPointer === null || undoUnderPointer === void 0 ? void 0 : undoUnderPointer();
undoGlobalBlock();
}

@@ -97,0 +94,0 @@ var unbindEvents = bindAll(window, [{

{
"name": "@atlaskit/drag-and-drop",
"version": "0.13.0",
"version": "0.14.0",
"sideEffects": false
}

@@ -101,2 +101,3 @@ export declare type CleanupFn = () => void;

* Where the user was previously.
* `previous` points to what `current` was in the last dispatched event
*

@@ -106,2 +107,7 @@ * `previous` is particularly useful for `onDropTargetChange`

* as you can know what the delta of the change
*
* Exception: `onGenerateDragPreview` and `onDragStart` will have the
* same `current` and `previous` values. This is done so that the data
* received in `onDragStart` feels logical
* (`location.previous` should be `[]` in `onDragStart`)
*/

@@ -108,0 +114,0 @@ previous: Pick<DragLocation, 'dropTargets'>;

@@ -1,4 +0,5 @@

import { AllDragTypes, DragLocationHistory, EventPayloadMap } from '../internal-types';
export declare function makeDispatch<DragType extends AllDragTypes>({ source, dispatchEvent, }: {
import { AllDragTypes, DragLocation, EventPayloadMap } from '../internal-types';
export declare function makeDispatch<DragType extends AllDragTypes>({ source, initial, dispatchEvent, }: {
source: DragType['payload'];
initial: DragLocation;
dispatchEvent: <EventName extends keyof EventPayloadMap<DragType>>(args: {

@@ -9,14 +10,13 @@ eventName: EventName;

}): {
start({ location, nativeSetDragImage, }: {
location: DragLocationHistory;
start({ nativeSetDragImage, }: {
nativeSetDragImage: DataTransfer['setDragImage'] | null;
}): void;
dragUpdate({ location }: {
location: DragLocationHistory;
dragUpdate({ current }: {
current: DragLocation;
}): void;
drag({ location }: {
location: DragLocationHistory;
drag({ current }: {
current: DragLocation;
}): void;
drop({ location, updatedExternalPayload: updatedSourcePayload, }: {
location: DragLocationHistory;
drop({ current, updatedExternalPayload: updatedSourcePayload, }: {
current: DragLocation;
/** When dragging from an external source, we need to collect the

@@ -23,0 +23,0 @@ drag source information again as it is often only available during

@@ -1,2 +0,2 @@

import { DragLocationHistory } from '../internal-types';
import type { DragLocation } from '../internal-types';
/** 🔥🤮 Fix (Chrome, Safari and Firefox) bug where the element under where the user started dragging

@@ -9,12 +9,7 @@ * (on the viewport) is entered into by the browser after a drag finishes ("drop" or "dragend")

*
* Conceptually this is what we are doing (but without adding any new style rules to the page)
* ```css
* body { pointer-events: none; }
* elementUnderPointer { pointer-events: auto; }
* elementUnderPointer > * { pointer-events: none; }
* ```
*
* - [Visual explanation of bug](https://twitter.com/alexandereardon/status/1633614212873465856)
* - [Chrome bug](https://bugs.chromium.org/p/chromium/issues/detail?id=410328)
*/
export declare function fixPostDragPointerBug(location: DragLocationHistory): void;
export declare function fixPostDragPointerBug({ current }: {
current: DragLocation;
}): void;
{
"name": "@atlaskit/drag-and-drop",
"version": "0.13.0",
"version": "0.14.0",
"description": "The core Atlassian drag and drop framework, optimized for performance.",

@@ -5,0 +5,0 @@ "repository": "https://bitbucket.org/atlassian/atlassian-frontend-mirror",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc