Commit aa89aa7d authored by John Lee's avatar John Lee Committed by Commit Bot

WebUI Tab Strip: Update tab moves on drop, not drag update

This CLs updates the drag and drop interactions such that only
the DOM is updated as the user drags a tab or group element. The
tab strip model is only changed once the user commits the move by
dropping the element in place.

The DragManager now initializes a new DragSession every time a
drag begins, and uses the session to store and retrieve indices.

This will also make it easier and more consistent to drop a tab or
group from another window into a specific index. The idea is to create
a TabElement or TabGroupElement as a drag enters from another
window and keep most of the code the same.

Bug: 1048894
Change-Id: I92e9866ce2b57aae2736fa81b7a5e9e1b334cdfe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2065329Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Commit-Queue: John Lee <johntlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#743378}
parent c6c55006
...@@ -35,6 +35,20 @@ export class DragManagerDelegate { ...@@ -35,6 +35,20 @@ export class DragManagerDelegate {
*/ */
getIndexOfTab(tabElement) {} getIndexOfTab(tabElement) {}
/**
* @param {!TabElement} element
* @param {number} index
* @param {boolean} pinned
* @param {string|undefined} groupId
*/
placeTabElement(element, index, pinned, groupId) {}
/**
* @param {!TabGroupElement} element
* @param {number} index
*/
placeTabGroupElement(element, index) {}
/** @param {!Element} element */ /** @param {!Element} element */
showDropPlaceholder(element) {} showDropPlaceholder(element) {}
} }
...@@ -42,44 +56,137 @@ export class DragManagerDelegate { ...@@ -42,44 +56,137 @@ export class DragManagerDelegate {
/** @typedef {!DragManagerDelegate|!HTMLElement} */ /** @typedef {!DragManagerDelegate|!HTMLElement} */
let DragManagerDelegateElement; let DragManagerDelegateElement;
export class DragManager { class DragSession {
/** @param {!DragManagerDelegateElement} delegate */ /**
constructor(delegate) { * @param {!DragManagerDelegateElement} delegate
/** @private {!DragManagerDelegateElement} */ * @param {!TabElement|!TabGroupElement} element
* @param {number} srcIndex
* @param {string=} srcGroup
*/
constructor(delegate, element, srcIndex, srcGroup) {
/** @const @private {!DragManagerDelegateElement} */
this.delegate_ = delegate; this.delegate_ = delegate;
/** /** @const {!TabElement|!TabGroupElement} */
* The element currently being dragged. this.element_ = element;
* @type {?TabElement|?TabGroupElement}
*/
this.draggedItem_ = null;
/** @type {!Element} */ /** @const {number} */
this.dropPlaceholder_ = document.createElement('div'); this.srcIndex = srcIndex;
this.dropPlaceholder_.id = 'dropPlaceholder';
/** @private {!TabsApiProxy} */ /** @const {string|undefined} */
this.srcGroup = srcGroup;
/** @private @const {!TabsApiProxy} */
this.tabsProxy_ = TabsApiProxy.getInstance(); this.tabsProxy_ = TabsApiProxy.getInstance();
} }
cancelDrag() { /**
this.dropPlaceholder_.remove(); * @param {!DragManagerDelegateElement} delegate
* @param {!TabElement|!TabGroupElement} element
* @return {!DragSession}
*/
static createFromElement(delegate, element) {
if (isTabGroupElement(element)) {
return new DragSession(
delegate, element,
delegate.getIndexOfTab(
/** @type {!TabElement} */ (element.firstElementChild)));
}
const srcIndex = delegate.getIndexOfTab(
/** @type {!TabElement} */ (element));
const srcGroup =
(element.parentElement && isTabGroupElement(element.parentElement)) ?
element.parentElement.dataset.groupId :
undefined;
return new DragSession(delegate, element, srcIndex, srcGroup);
}
/** @return {string|undefined} */
get dstGroup() {
if (isTabElement(this.element_) && this.element_.parentElement &&
isTabGroupElement(this.element_.parentElement)) {
return this.element_.parentElement.dataset.groupId;
}
return undefined;
}
/** @return {number} */
get dstIndex() {
if (isTabElement(this.element_)) {
return this.delegate_.getIndexOfTab(
/** @type {!TabElement} */ (this.element_));
}
// If a tab group is moving backwards (to the front of the tab strip), the
// new index is the index of the first tab in that group. If a tab group is
// moving forwards (to the end of the tab strip), the new index is the index
// of the last tab in that group.
let dstIndex = this.delegate_.getIndexOfTab(
/** @type {!TabElement} */ (this.element_.firstElementChild));
if (this.srcIndex <= dstIndex) {
dstIndex += this.element_.childElementCount - 1;
}
return dstIndex;
}
cancel() {
if (isTabGroupElement(this.element_)) {
this.delegate_.placeTabGroupElement(
/** @type {!TabGroupElement} */ (this.element_), this.srcIndex);
} else if (isTabElement(this.element_)) {
this.delegate_.placeTabElement(
/** @type {!TabElement} */ (this.element_), this.srcIndex,
this.element_.tab.pinned, this.srcGroup);
}
this.element_.setDragging(false);
}
finish() {
const dstGroupId = this.dstGroup;
if (dstGroupId && dstGroupId !== this.srcGroup) {
this.tabsProxy_.groupTab(this.element_.tab.id, dstGroupId);
} else if (!dstGroupId && this.srcGroup) {
this.tabsProxy_.ungroupTab(this.element_.tab.id);
}
const dstIndex = this.dstIndex;
if (isTabElement(this.element_)) {
this.tabsProxy_.moveTab(this.element_.tab.id, dstIndex);
} else if (isTabGroupElement(this.element_)) {
this.tabsProxy_.moveGroup(this.element_.dataset.groupId, dstIndex);
}
this.element_.setDragging(false);
} }
/** @param {!DragEvent} event */ /** @param {!DragEvent} event */
continueDrag(event) { start(event) {
event.preventDefault(); event.dataTransfer.effectAllowed = 'move';
const draggedItemRect = this.element_.getBoundingClientRect();
this.element_.setDragging(true);
event.dataTransfer.setDragImage(
this.element_.getDragImage(), event.clientX - draggedItemRect.left,
event.clientY - draggedItemRect.top);
if (!this.draggedItem_) { if (isTabElement(this.element_)) {
this.delegate_.showDropPlaceholder(this.dropPlaceholder_); event.dataTransfer.setData(
return; getTabIdDataType(), this.element_.tab.id.toString());
} else if (isTabGroupElement(this.element_)) {
event.dataTransfer.setData(
getGroupIdDataType(), this.element_.dataset.groupId);
} }
}
/** @param {!DragEvent} event */
update(event) {
event.dataTransfer.dropEffect = 'move'; event.dataTransfer.dropEffect = 'move';
if (isTabGroupElement(this.draggedItem_)) { if (isTabGroupElement(this.element_)) {
this.continueDragWithGroupElement_(event); this.updateForTabGroupElement_(event);
} else if (isTabElement(this.draggedItem_)) { } else if (isTabElement(this.element_)) {
this.continueDragWithTabElement_(event); this.updateForTabElement_(event);
} }
} }
...@@ -87,9 +194,11 @@ export class DragManager { ...@@ -87,9 +194,11 @@ export class DragManager {
* @param {!DragEvent} event * @param {!DragEvent} event
* @private * @private
*/ */
continueDragWithGroupElement_(event) { updateForTabGroupElement_(event) {
const tabGroupElement =
/** @type {!TabGroupElement} */ (this.element_);
const composedPath = /** @type {!Array<!Element>} */ (event.composedPath()); const composedPath = /** @type {!Array<!Element>} */ (event.composedPath());
if (composedPath.includes(assert(this.draggedItem_))) { if (composedPath.includes(assert(this.element_))) {
// Dragging over itself or a child of itself. // Dragging over itself or a child of itself.
return; return;
} }
...@@ -98,8 +207,7 @@ export class DragManager { ...@@ -98,8 +207,7 @@ export class DragManager {
/** @type {!TabElement|undefined} */ (composedPath.find(isTabElement)); /** @type {!TabElement|undefined} */ (composedPath.find(isTabElement));
if (dragOverTabElement && !dragOverTabElement.tab.pinned) { if (dragOverTabElement && !dragOverTabElement.tab.pinned) {
const dragOverIndex = this.delegate_.getIndexOfTab(dragOverTabElement); const dragOverIndex = this.delegate_.getIndexOfTab(dragOverTabElement);
this.tabsProxy_.moveGroup( this.delegate_.placeTabGroupElement(tabGroupElement, dragOverIndex);
this.draggedItem_.dataset.groupId, dragOverIndex);
return; return;
} }
...@@ -107,8 +215,7 @@ export class DragManager { ...@@ -107,8 +215,7 @@ export class DragManager {
if (dragOverGroupElement) { if (dragOverGroupElement) {
const dragOverIndex = this.delegate_.getIndexOfTab( const dragOverIndex = this.delegate_.getIndexOfTab(
/** @type {!TabElement} */ (dragOverGroupElement.firstElementChild)); /** @type {!TabElement} */ (dragOverGroupElement.firstElementChild));
this.tabsProxy_.moveGroup( this.delegate_.placeTabGroupElement(tabGroupElement, dragOverIndex);
this.draggedItem_.dataset.groupId, dragOverIndex);
} }
} }
...@@ -116,27 +223,34 @@ export class DragManager { ...@@ -116,27 +223,34 @@ export class DragManager {
* @param {!DragEvent} event * @param {!DragEvent} event
* @private * @private
*/ */
continueDragWithTabElement_(event) { updateForTabElement_(event) {
const tabElement = /** @type {!TabElement} */ (this.element_);
const composedPath = /** @type {!Array<!Element>} */ (event.composedPath()); const composedPath = /** @type {!Array<!Element>} */ (event.composedPath());
const dragOverTabElement = const dragOverTabElement =
/** @type {?TabElement} */ (composedPath.find(isTabElement)); /** @type {?TabElement} */ (composedPath.find(isTabElement));
if (dragOverTabElement && if (dragOverTabElement &&
dragOverTabElement.tab.pinned !== this.draggedItem_.tab.pinned) { dragOverTabElement.tab.pinned !== tabElement.tab.pinned) {
// Can only drag between the same pinned states. // Can only drag between the same pinned states.
return; return;
} }
const previousGroupId = (tabElement.parentElement &&
isTabGroupElement(tabElement.parentElement)) ?
tabElement.parentElement.dataset.groupId :
undefined;
const dragOverTabGroup = const dragOverTabGroup =
/** @type {?TabGroupElement} */ (composedPath.find(isTabGroupElement)); /** @type {?TabGroupElement} */ (composedPath.find(isTabGroupElement));
if (dragOverTabGroup && if (dragOverTabGroup &&
dragOverTabGroup.dataset.groupId !== this.draggedItem_.tab.groupId) { dragOverTabGroup.dataset.groupId !== previousGroupId) {
this.tabsProxy_.groupTab( this.delegate_.placeTabElement(
this.draggedItem_.tab.id, dragOverTabGroup.dataset.groupId); tabElement, this.dstIndex, false, dragOverTabGroup.dataset.groupId);
return; return;
} }
if (!dragOverTabGroup && this.draggedItem_.tab.groupId) { if (!dragOverTabGroup && previousGroupId) {
this.tabsProxy_.ungroupTab(this.draggedItem_.tab.id); this.delegate_.placeTabElement(
tabElement, this.dstIndex, false, undefined);
return; return;
} }
...@@ -145,36 +259,51 @@ export class DragManager { ...@@ -145,36 +259,51 @@ export class DragManager {
} }
const dragOverIndex = this.delegate_.getIndexOfTab(dragOverTabElement); const dragOverIndex = this.delegate_.getIndexOfTab(dragOverTabElement);
this.tabsProxy_.moveTab(this.draggedItem_.tab.id, dragOverIndex); this.delegate_.placeTabElement(
tabElement, dragOverIndex, tabElement.tab.pinned, previousGroupId);
} }
}
/** export class DragManager {
* @param {!DragEvent} event /** @param {!DragManagerDelegateElement} delegate */
*/ constructor(delegate) {
drop(event) { /** @private {!DragManagerDelegateElement} */
if (this.draggedItem_) { this.delegate_ = delegate;
// If there is a valid dragged item, the drag originated from this TabList
// and is handled already by previous dragover events. /** @type {?DragSession} */
return; this.dragSession_ = null;
}
/** @type {!Element} */
this.dropPlaceholder_ = document.createElement('div');
this.dropPlaceholder_.id = 'dropPlaceholder';
/** @private {!TabsApiProxy} */
this.tabsProxy_ = TabsApiProxy.getInstance();
}
/** @private */
onDragLeave_() {
// TODO(johntlee): Handle drag and drop from other windows with
// DragSession.
this.dropPlaceholder_.remove(); this.dropPlaceholder_.remove();
}
if (event.dataTransfer.types.includes(getTabIdDataType())) { /** @param {!DragEvent} event */
const tabId = Number(event.dataTransfer.getData(getTabIdDataType())); onDragOver_(event) {
if (Number.isNaN(tabId)) { event.preventDefault();
// Invalid tab ID. Return silently.
return; if (!this.dragSession_) {
} // TODO(johntlee): Handle drag and drop from other windows with
this.tabsProxy_.moveTab(tabId, -1); // DragSession.
} else if (event.dataTransfer.types.includes(getGroupIdDataType())) { this.delegate_.showDropPlaceholder(this.dropPlaceholder_);
const groupId = event.dataTransfer.getData(getGroupIdDataType()); return;
this.tabsProxy_.moveGroup(groupId, -1);
} }
this.dragSession_.update(event);
} }
/** @param {!DragEvent} event */ /** @param {!DragEvent} event */
startDrag(event) { onDragStart_(event) {
const draggedItem = const draggedItem =
/** @type {!Array<!Element>} */ (event.composedPath()).find(item => { /** @type {!Array<!Element>} */ (event.composedPath()).find(item => {
return isTabElement(item) || isTabGroupElement(item); return isTabElement(item) || isTabGroupElement(item);
...@@ -183,30 +312,56 @@ export class DragManager { ...@@ -183,30 +312,56 @@ export class DragManager {
return; return;
} }
this.draggedItem_ = /** @type {!TabElement} */ (draggedItem); this.dragSession_ = DragSession.createFromElement(
event.dataTransfer.effectAllowed = 'move'; this.delegate_,
const draggedItemRect = this.draggedItem_.getBoundingClientRect(); /** @type {!TabElement|!TabGroupElement} */ (draggedItem));
this.draggedItem_.setDragging(true); this.dragSession_.start(event);
event.dataTransfer.setDragImage( }
this.draggedItem_.getDragImage(), event.clientX - draggedItemRect.left,
event.clientY - draggedItemRect.top);
if (isTabElement(draggedItem)) { /** @param {!DragEvent} event */
event.dataTransfer.setData( onDragEnd_(event) {
getTabIdDataType(), this.draggedItem_.tab.id.toString()); if (!this.dragSession_) {
} else if (isTabGroupElement(draggedItem)) { return;
event.dataTransfer.setData(
getGroupIdDataType(), this.draggedItem_.dataset.groupId);
} }
this.dragSession_.cancel();
this.dragSession_ = null;
} }
/** @param {!DragEvent} event */ /**
stopDrag(event) { * @param {!DragEvent} event
if (!this.draggedItem_) { */
onDrop_(event) {
if (this.dragSession_) {
this.dragSession_.finish();
this.dragSession_ = null;
return; return;
} }
this.draggedItem_.setDragging(false); // TODO(johntlee): Handle drag and drop from other windows with DragSession.
this.draggedItem_ = null; this.dropPlaceholder_.remove();
if (event.dataTransfer.types.includes(getTabIdDataType())) {
const tabId = Number(event.dataTransfer.getData(getTabIdDataType()));
if (Number.isNaN(tabId)) {
// Invalid tab ID. Return silently.
return;
}
this.tabsProxy_.moveTab(tabId, -1);
} else if (event.dataTransfer.types.includes(getGroupIdDataType())) {
const groupId = event.dataTransfer.getData(getGroupIdDataType());
this.tabsProxy_.moveGroup(groupId, -1);
}
}
startObserving() {
this.delegate_.addEventListener(
'dragstart', e => this.onDragStart_(/** @type {!DragEvent} */ (e)));
this.delegate_.addEventListener(
'dragend', e => this.onDragEnd_(/** @type {!DragEvent} */ (e)));
this.delegate_.addEventListener('dragleave', () => this.onDragLeave_());
this.delegate_.addEventListener(
'dragover', e => this.onDragOver_(/** @type {!DragEvent} */ (e)));
this.delegate_.addEventListener(
'drop', e => this.onDrop_(/** @type {!DragEvent} */ (e)));
} }
} }
...@@ -4,6 +4,11 @@ ...@@ -4,6 +4,11 @@
--tabstrip-tab-group-title-margin: var(--tabstrip-tab-spacing); --tabstrip-tab-group-title-margin: var(--tabstrip-tab-spacing);
} }
:host(:empty) {
/* A tab group can temporarily become empty as a tab is being dragged out. */
display: none;
}
#tabGroup { #tabGroup {
border-radius: 8px; border-radius: 8px;
box-shadow: 0 0 0 1px rgba(var(--tabstrip-tab-group-color-rgb), .24); box-shadow: 0 0 0 1px rgba(var(--tabstrip-tab-group-color-rgb), .24);
......
...@@ -163,17 +163,7 @@ class TabListElement extends CustomElement { ...@@ -163,17 +163,7 @@ class TabListElement extends CustomElement {
}); });
const dragManager = new DragManager(this); const dragManager = new DragManager(this);
this.addEventListener( dragManager.startObserving();
'dragstart', e => dragManager.startDrag(/** @type {!DragEvent} */ (e)));
this.addEventListener(
'dragend', e => dragManager.stopDrag(/** @type {!DragEvent} */ (e)));
this.addEventListener('dragleave', () => dragManager.cancelDrag());
this.addEventListener(
'dragover',
e => dragManager.continueDrag(
/** @type {!DragEvent} */ (e)));
this.addEventListener(
'drop', e => dragManager.drop(/** @type {!DragEvent} */ (e)));
if (loadTimeData.getBoolean('showDemoOptions')) { if (loadTimeData.getBoolean('showDemoOptions')) {
this.$('#demoOptions').style.display = 'block'; this.$('#demoOptions').style.display = 'block';
......
...@@ -10,15 +10,24 @@ import {TabsApiProxy} from 'chrome://tab-strip/tabs_api_proxy.js'; ...@@ -10,15 +10,24 @@ import {TabsApiProxy} from 'chrome://tab-strip/tabs_api_proxy.js';
import {TestTabsApiProxy} from './test_tabs_api_proxy.js'; import {TestTabsApiProxy} from './test_tabs_api_proxy.js';
class MockDelegate extends HTMLElement { class MockDelegate extends HTMLElement {
constructor() {
super();
}
getIndexOfTab(tabElement) { getIndexOfTab(tabElement) {
return Array.from(this.querySelectorAll('tabstrip-tab')) return Array.from(this.querySelectorAll('tabstrip-tab'))
.indexOf(tabElement); .indexOf(tabElement);
} }
placeTabElement(element, index, pinned, groupId) {
element.remove();
const parent =
groupId ? this.querySelector(`[data-group-id=${groupId}]`) : this;
parent.insertBefore(element, this.children[index]);
}
placeTabGroupElement(element, index) {
element.remove();
this.insertBefore(element, this.children[index]);
}
showDropPlaceholder(element) { showDropPlaceholder(element) {
this.appendChild(element); this.appendChild(element);
} }
...@@ -117,11 +126,7 @@ suite('DragManager', () => { ...@@ -117,11 +126,7 @@ suite('DragManager', () => {
delegate.appendChild(tabElement); delegate.appendChild(tabElement);
}); });
dragManager = new DragManager(delegate); dragManager = new DragManager(delegate);
delegate.addEventListener('dragstart', e => dragManager.startDrag(e)); dragManager.startObserving();
delegate.addEventListener('dragend', e => dragManager.stopDrag(e));
delegate.addEventListener('dragleave', () => dragManager.cancelDrag());
delegate.addEventListener('dragover', (e) => dragManager.continueDrag(e));
delegate.addEventListener('drop', (e) => dragManager.drop(e));
}); });
test('DragStartSetsDragImage', () => { test('DragStartSetsDragImage', () => {
...@@ -151,7 +156,7 @@ suite('DragManager', () => { ...@@ -151,7 +156,7 @@ suite('DragManager', () => {
const dragOverTab = delegate.children[dragOverIndex]; const dragOverTab = delegate.children[dragOverIndex];
const mockDataTransfer = new MockDataTransfer(); const mockDataTransfer = new MockDataTransfer();
// Dispatch a dragstart event to start the drag process // Dispatch a dragstart event to start the drag process.
const dragStartEvent = new DragEvent('dragstart', { const dragStartEvent = new DragEvent('dragstart', {
bubbles: true, bubbles: true,
composed: true, composed: true,
...@@ -161,7 +166,7 @@ suite('DragManager', () => { ...@@ -161,7 +166,7 @@ suite('DragManager', () => {
}); });
draggedTab.dispatchEvent(dragStartEvent); draggedTab.dispatchEvent(dragStartEvent);
// Move the draggedTab over the 2nd tab // Move the draggedTab over the 2nd tab.
const dragOverEvent = new DragEvent('dragover', { const dragOverEvent = new DragEvent('dragover', {
bubbles: true, bubbles: true,
composed: true, composed: true,
...@@ -169,6 +174,12 @@ suite('DragManager', () => { ...@@ -169,6 +174,12 @@ suite('DragManager', () => {
}); });
dragOverTab.dispatchEvent(dragOverEvent); dragOverTab.dispatchEvent(dragOverEvent);
assertEquals(dragOverEvent.dataTransfer.dropEffect, 'move'); assertEquals(dragOverEvent.dataTransfer.dropEffect, 'move');
// Dragover tab and dragged tab have now switched places in the DOM.
assertEquals(draggedTab, delegate.children[dragOverIndex]);
assertEquals(dragOverTab, delegate.children[draggedIndex]);
draggedTab.dispatchEvent(new DragEvent('drop', {bubbles: true}));
const [tabId, newIndex] = await testTabsApiProxy.whenCalled('moveTab'); const [tabId, newIndex] = await testTabsApiProxy.whenCalled('moveTab');
assertEquals(tabId, tabs[draggedIndex].id); assertEquals(tabId, tabs[draggedIndex].id);
assertEquals(newIndex, dragOverIndex); assertEquals(newIndex, dragOverIndex);
...@@ -199,6 +210,11 @@ suite('DragManager', () => { ...@@ -199,6 +210,11 @@ suite('DragManager', () => {
dataTransfer: mockDataTransfer, dataTransfer: mockDataTransfer,
}); });
dragOverTabGroup.dispatchEvent(dragOverEvent); dragOverTabGroup.dispatchEvent(dragOverEvent);
// Tab is now in the group within the DOM.
assertEquals(dragOverTabGroup, draggedTab.parentElement);
draggedTab.dispatchEvent(new DragEvent('drop', {bubbles: true}));
const [tabId, groupId] = await testTabsApiProxy.whenCalled('groupTab'); const [tabId, groupId] = await testTabsApiProxy.whenCalled('groupTab');
assertEquals(draggedTab.tab.id, tabId); assertEquals(draggedTab.tab.id, tabId);
assertEquals('group0', groupId); assertEquals('group0', groupId);
...@@ -227,6 +243,11 @@ suite('DragManager', () => { ...@@ -227,6 +243,11 @@ suite('DragManager', () => {
dataTransfer: mockDataTransfer, dataTransfer: mockDataTransfer,
}); });
delegate.dispatchEvent(dragOverEvent); delegate.dispatchEvent(dragOverEvent);
// The tab is now outside of the group in the DOM.
assertEquals(delegate, draggedTab.parentElement);
draggedTab.dispatchEvent(new DragEvent('drop', {bubbles: true}));
const [tabId] = await testTabsApiProxy.whenCalled('ungroupTab'); const [tabId] = await testTabsApiProxy.whenCalled('ungroupTab');
assertEquals(draggedTab.tab.id, tabId); assertEquals(draggedTab.tab.id, tabId);
}); });
...@@ -235,7 +256,8 @@ suite('DragManager', () => { ...@@ -235,7 +256,8 @@ suite('DragManager', () => {
const tabElements = delegate.children; const tabElements = delegate.children;
// Start dragging the group. // Start dragging the group.
const draggedGroup = groupTab(tabElements[0], 'group0'); const draggedGroupIndex = 0;
const draggedGroup = groupTab(tabElements[draggedGroupIndex], 'group0');
const mockDataTransfer = new MockDataTransfer(); const mockDataTransfer = new MockDataTransfer();
const dragStartEvent = new DragEvent('dragstart', { const dragStartEvent = new DragEvent('dragstart', {
bubbles: true, bubbles: true,
...@@ -247,13 +269,20 @@ suite('DragManager', () => { ...@@ -247,13 +269,20 @@ suite('DragManager', () => {
draggedGroup.dispatchEvent(dragStartEvent); draggedGroup.dispatchEvent(dragStartEvent);
// Drag the group over the second tab. // Drag the group over the second tab.
const dragOverTab = tabElements[1]; const dragOverIndex = 1;
const dragOverTab = tabElements[dragOverIndex];
const dragOverEvent = new DragEvent('dragover', { const dragOverEvent = new DragEvent('dragover', {
bubbles: true, bubbles: true,
composed: true, composed: true,
dataTransfer: mockDataTransfer, dataTransfer: mockDataTransfer,
}); });
dragOverTab.dispatchEvent(dragOverEvent); dragOverTab.dispatchEvent(dragOverEvent);
// Group and tab have now switched places.
assertEquals(draggedGroup, delegate.children[dragOverIndex]);
assertEquals(dragOverTab, delegate.children[draggedGroupIndex]);
draggedGroup.dispatchEvent(new DragEvent('drop', {bubbles: true}));
const [groupId, index] = await testTabsApiProxy.whenCalled('moveGroup'); const [groupId, index] = await testTabsApiProxy.whenCalled('moveGroup');
assertEquals('group0', groupId); assertEquals('group0', groupId);
assertEquals(1, index); assertEquals(1, index);
...@@ -263,8 +292,10 @@ suite('DragManager', () => { ...@@ -263,8 +292,10 @@ suite('DragManager', () => {
const tabElements = delegate.children; const tabElements = delegate.children;
// Group the first tab and second tab separately. // Group the first tab and second tab separately.
const draggedGroup = groupTab(tabElements[0], 'group0'); const draggedIndex = 0;
const dragOverGroup = groupTab(tabElements[1], 'group1'); const draggedGroup = groupTab(tabElements[draggedIndex], 'group0');
const dragOverIndex = 1;
const dragOverGroup = groupTab(tabElements[dragOverIndex], 'group1');
// Start dragging the first group. // Start dragging the first group.
const mockDataTransfer = new MockDataTransfer(); const mockDataTransfer = new MockDataTransfer();
...@@ -284,6 +315,12 @@ suite('DragManager', () => { ...@@ -284,6 +315,12 @@ suite('DragManager', () => {
dataTransfer: mockDataTransfer, dataTransfer: mockDataTransfer,
}); });
dragOverGroup.dispatchEvent(dragOverEvent); dragOverGroup.dispatchEvent(dragOverEvent);
// Groups have now switched places.
assertEquals(draggedGroup, delegate.children[dragOverIndex]);
assertEquals(dragOverGroup, delegate.children[draggedIndex]);
draggedGroup.dispatchEvent(new DragEvent('drop', {bubbles: true}));
const [groupId, index] = await testTabsApiProxy.whenCalled('moveGroup'); const [groupId, index] = await testTabsApiProxy.whenCalled('moveGroup');
assertEquals('group0', groupId); assertEquals('group0', groupId);
assertEquals(1, index); assertEquals(1, index);
...@@ -321,4 +358,31 @@ suite('DragManager', () => { ...@@ -321,4 +358,31 @@ suite('DragManager', () => {
assertEquals(droppedTabId, tabId); assertEquals(droppedTabId, tabId);
assertEquals(-1, index); assertEquals(-1, index);
}); });
test('CancelDragResetsPosition', () => {
const draggedIndex = 0;
const draggedTab = delegate.children[draggedIndex];
const mockDataTransfer = new MockDataTransfer();
// Dispatch a dragstart event to start the drag process.
const dragStartEvent = new DragEvent('dragstart', {
bubbles: true,
composed: true,
clientX: 100,
clientY: 150,
dataTransfer: mockDataTransfer,
});
draggedTab.dispatchEvent(dragStartEvent);
// Move the draggedTab over the 2nd tab.
const dragOverEvent = new DragEvent('dragover', {
bubbles: true,
composed: true,
dataTransfer: mockDataTransfer,
});
delegate.children[1].dispatchEvent(dragOverEvent);
draggedTab.dispatchEvent(new DragEvent('dragend', {bubbles: true}));
assertEquals(draggedTab, delegate.children[draggedIndex]);
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment