-
Notifications
You must be signed in to change notification settings - Fork 264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PoC: Table Drag & Drop #9955
base: main
Are you sure you want to change the base?
PoC: Table Drag & Drop #9955
Changes from all commits
633fd80
bf79aa9
f31fe13
5019b4f
bc39268
15e30e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import type UI5Element from "../../UI5Element.js"; | ||
import type MovePlacement from "../../types/MovePlacement.js"; | ||
import DragRegistry from "./DragRegistry.js"; | ||
import { findClosestPosition } from "./findClosestPosition.js"; | ||
import Orientation from "../../types/Orientation.js"; | ||
|
||
type DragOverResult = { | ||
targetReference: HTMLElement | null; | ||
placement: any; | ||
} | ||
|
||
/** | ||
* Drags an element over a list of items and returns the target reference and the placement of the dragged element. | ||
* | ||
* @param e {DragEvent} The drag event. | ||
* @param component {UI5Element} The component, which contains the items. | ||
* @param items {Array<HTMLElement>} The items, which the dragged element is dragged over. | ||
* @returns {DragOverResult} The target reference and the placement of the dragged element. | ||
*/ | ||
function dragOver(e: DragEvent, component: UI5Element, items: Array<HTMLElement>): DragOverResult { | ||
const dragOverResult: DragOverResult = { | ||
targetReference: null, | ||
placement: null, | ||
}; | ||
|
||
const draggedElement = DragRegistry.getDraggedElement(); | ||
|
||
if (!(e.target instanceof HTMLElement) || !draggedElement) { | ||
return dragOverResult; | ||
} | ||
|
||
const closestPosition = findClosestPosition( | ||
items, | ||
e.clientY, | ||
Orientation.Vertical, | ||
); | ||
Comment on lines
+21
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although this code is the same for List and Table, it is not for the Tree and TabContainer. Maybe put this back to the components and only reuse the code below? |
||
|
||
if (closestPosition) { | ||
const placements = closestPosition.placements; | ||
dragOverResult.targetReference = e.target; | ||
|
||
const placementAccepted = placements.some(placement => { | ||
const beforeItemMovePrevented = !component.fireEvent("move-over", { | ||
originalEvent: e, | ||
source: { | ||
element: draggedElement, | ||
}, | ||
destination: { | ||
element: closestPosition.element, | ||
placement, | ||
}, | ||
}, true); | ||
|
||
if (beforeItemMovePrevented) { | ||
e.preventDefault(); | ||
dragOverResult.targetReference = closestPosition.element; | ||
dragOverResult.placement = placement; | ||
return true; | ||
} | ||
|
||
return false; | ||
}); | ||
|
||
if (!placementAccepted) { | ||
dragOverResult.targetReference = null; | ||
} | ||
} | ||
|
||
return dragOverResult; | ||
} | ||
|
||
function dropRow(e: DragEvent, component: UI5Element, target: HTMLElement, placement: `${MovePlacement}`): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thumbs up for making this reusable. It will foster proper maintenance of the DragRegistry and can be directly reused by Tree and TabContainer. |
||
e.preventDefault(); | ||
const draggedElement = DragRegistry.getDraggedElement(); | ||
|
||
if (!draggedElement) { | ||
return; | ||
} | ||
|
||
component.fireEvent("move", { | ||
originalEvent: e, | ||
source: { | ||
element: draggedElement, | ||
}, | ||
destination: { | ||
element: target, | ||
placement, | ||
}, | ||
}); | ||
|
||
draggedElement?.focus(); | ||
} | ||
|
||
export { | ||
DragOverResult, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems unnecessary to export |
||
dragOver, | ||
dropRow, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
import { findClosestPosition } from "@ui5/webcomponents-base/dist/util/dragAndDrop/findClosestPosition.js"; | ||
import Orientation from "@ui5/webcomponents-base/dist/types/Orientation.js"; | ||
import DragRegistry from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRegistry.js"; | ||
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; | ||
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; | ||
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; | ||
|
@@ -10,6 +13,7 @@ import type { ResizeObserverCallback } from "@ui5/webcomponents-base/dist/delega | |
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; | ||
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; | ||
import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; | ||
import type MovePlacement from "@ui5/webcomponents-base/dist/types/MovePlacement.js"; | ||
import TableTemplate from "./generated/templates/TableTemplate.lit.js"; | ||
import TableStyles from "./generated/themes/Table.css.js"; | ||
import TableRow from "./TableRow.js"; | ||
|
@@ -19,12 +23,14 @@ import TableExtension from "./TableExtension.js"; | |
import type TableSelection from "./TableSelection.js"; | ||
import TableOverflowMode from "./types/TableOverflowMode.js"; | ||
import TableNavigation from "./TableNavigation.js"; | ||
import DropIndicator from "./DropIndicator.js"; | ||
import { | ||
TABLE_NO_DATA, | ||
} from "./generated/i18n/i18n-defaults.js"; | ||
import BusyIndicator from "./BusyIndicator.js"; | ||
import TableCell from "./TableCell.js"; | ||
import { findVerticalScrollContainer, scrollElementIntoView, isFeature } from "./TableUtils.js"; | ||
import { dragOver, dropRow } from "@ui5/webcomponents-base/dist/util/dragAndDrop/DragRowUtil.js"; | ||
|
||
/** | ||
* Interface for components that can be slotted inside the <code>features</code> slot of the <code>ui5-table</code>. | ||
|
@@ -72,6 +78,17 @@ type TableRowClickEventDetail = { | |
row: TableRow, | ||
}; | ||
|
||
type TableMoveEventDetail = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should also describe the |
||
originalEvent: Event, | ||
source: { | ||
element: HTMLElement, | ||
}, | ||
destination: { | ||
element: HTMLElement, | ||
placement: `${MovePlacement}`, | ||
} | ||
} | ||
|
||
/** | ||
* @class | ||
* | ||
|
@@ -164,6 +181,7 @@ type TableRowClickEventDetail = { | |
TableHeaderRow, | ||
TableCell, | ||
TableRow, | ||
DropIndicator, | ||
], | ||
}) | ||
|
||
|
@@ -322,6 +340,7 @@ class Table extends UI5Element { | |
this._events.forEach(eventType => this.addEventListener(eventType, this._onEventBound)); | ||
this.features.forEach(feature => feature.onTableActivate(this)); | ||
this._tableNavigation = new TableNavigation(this); | ||
DragRegistry.subscribe(this); | ||
} | ||
|
||
onExitDOM() { | ||
|
@@ -330,6 +349,7 @@ class Table extends UI5Element { | |
if (this.overflowMode === TableOverflowMode.Popin) { | ||
ResizeHandler.deregister(this, this._onResizeBound); | ||
} | ||
DragRegistry.unsubscribe(this); | ||
} | ||
|
||
onBeforeRendering(): void { | ||
|
@@ -462,6 +482,33 @@ class Table extends UI5Element { | |
this.fireEvent<TableRowClickEventDetail>("row-click", { row }); | ||
} | ||
|
||
_ondragenter(e: DragEvent) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _ondragenter, _ondragover, _ondragleave are pretty much the same implementation as in List.ts. So maybe there is a way to reuse the code in a Utility or something? |
||
e.preventDefault(); | ||
} | ||
|
||
_ondragleave(e: DragEvent) { | ||
if (e.relatedTarget instanceof Node && this.shadowRoot!.contains(e.relatedTarget)) { | ||
return; | ||
} | ||
|
||
this.dropIndicatorDOM!.targetReference = null; | ||
} | ||
|
||
_ondragover(e: DragEvent) { | ||
const { targetReference, placement } = dragOver(e, this, this.rows); | ||
this.dropIndicatorDOM!.targetReference = targetReference; | ||
this.dropIndicatorDOM!.placement = placement; | ||
} | ||
|
||
_ondrop(e: DragEvent) { | ||
if (!this.dropIndicatorDOM?.targetReference || !this.dropIndicatorDOM?.placement) { | ||
return; | ||
} | ||
|
||
dropRow(e, this, this.dropIndicatorDOM.targetReference, this.dropIndicatorDOM.placement); | ||
this.dropIndicatorDOM.targetReference = null; | ||
} | ||
|
||
get styles() { | ||
const headerStyleMap = this.headerRow?.[0]?.cells?.reduce((headerStyles, headerCell) => { | ||
if (headerCell.horizontalAlign !== undefined) { | ||
|
@@ -563,6 +610,10 @@ class Table extends UI5Element { | |
get isTable() { | ||
return true; | ||
} | ||
|
||
get dropIndicatorDOM(): DropIndicator | null { | ||
return this.shadowRoot!.querySelector("[ui5-drop-indicator]"); | ||
} | ||
} | ||
|
||
Table.define(); | ||
|
@@ -573,4 +624,5 @@ export type { | |
ITableFeature, | ||
ITableGrowing, | ||
TableRowClickEventDetail, | ||
TableMoveEventDetail, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,6 +78,15 @@ class TableRow extends TableRowBase { | |
@property({ type: Boolean }) | ||
navigated = false; | ||
|
||
/** | ||
* Defines whether the row is movable. | ||
* | ||
* @default false | ||
* @public | ||
*/ | ||
@property({ type: Boolean }) | ||
movable = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically not required, as you could just simply set But I guess, this would make sense for consistency across the API There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, please use
Last but not least, it won't work like that if you put the attribute to the root element in the shadow DOM. |
||
|
||
@property({ type: Boolean, noAttribute: true }) | ||
_renderNavigated = false; | ||
|
||
|
@@ -89,6 +98,11 @@ class TableRow extends TableRowBase { | |
} else { | ||
this.removeAttribute("aria-current"); | ||
} | ||
if (this.movable) { | ||
this.setAttribute("draggable", "true"); | ||
} else { | ||
this.removeAttribute("draggable"); | ||
} | ||
Comment on lines
+101
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better do this in TableRow.hbs, to encapsulate this attribute in a shadow DOM. |
||
} | ||
|
||
async focus(focusOptions?: FocusOptions | undefined): Promise<void> { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of this util. Please move both functions to separate files. Something like
handleDragOver.ts
andhandleDrop.ts