@3mo/data-grid
Version:
A data grid web component
174 lines (173 loc) • 6.54 kB
JavaScript
import { AsyncDirective, Controller, directive, noChange, PartType } from '@a11d/lit';
class DragGhostImage extends Image {
constructor() {
super(...arguments);
this.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==';
}
static { this.instance = new DragGhostImage(); }
}
export var ReorderabilityState;
(function (ReorderabilityState) {
ReorderabilityState["Idle"] = "idle";
ReorderabilityState["Dragging"] = "dragging";
ReorderabilityState["DropBefore"] = "drop-before";
ReorderabilityState["DropAfter"] = "drop-after";
})(ReorderabilityState || (ReorderabilityState = {}));
export class ReorderabilityController extends Controller {
constructor(host, options) {
super(host);
this.host = host;
this.options = options;
this.items = new Map();
}
get item() {
const controller = this;
return directive(class extends AsyncDirective {
constructor(partInfo) {
super(partInfo);
if (partInfo.type !== PartType.ELEMENT) {
throw new Error('This directive can only be used on an element');
}
}
render(options) {
options;
return noChange;
}
update(part, [options]) {
this.part = part;
this.options = options;
const element = part.element;
controller.items.set(element, options);
element.draggable = !options.disabled;
element.dataset.reorderability = this.state;
this.addEventListeners();
return noChange;
}
get state() {
const draggingItem = controller.draggingItem;
switch (true) {
case draggingItem === undefined:
return ReorderabilityState.Idle;
case draggingItem.source === this.options.index:
return ReorderabilityState.Dragging;
case draggingItem.destination !== this.options.index:
return ReorderabilityState.Idle;
case draggingItem.source > this.options.index:
return ReorderabilityState.DropBefore;
default:
return ReorderabilityState.DropAfter;
}
}
disconnected() {
controller.items.delete(this.part.element);
this.removeEventListeners();
}
reconnected() {
this.addEventListeners();
}
addEventListeners() {
if (!this.part)
return;
const element = this.part.element;
element.addEventListener('dragstart', controller, { capture: true });
element.addEventListener('dragover', controller, { capture: true });
element.addEventListener('drop', controller, { capture: true });
element.addEventListener('dragend', controller, { capture: true });
}
removeEventListeners() {
if (!this.part)
return;
const element = this.part.element;
element.removeEventListener('dragstart', controller);
element.removeEventListener('dragover', controller);
element.removeEventListener('drop', controller);
element.removeEventListener('dragend', controller);
}
});
}
handleEvent(e) {
if (this.items.get(e.currentTarget)?.disabled) {
return;
}
switch (e.type) {
case 'dragstart':
return this.handleDragStart(e);
case 'dragover':
return this.handleDropOver(e);
case 'drop':
return this.handleDrop(e);
case 'dragend':
return this.handleDragEnd(e);
}
}
handleDragStart(e) {
const index = this.items.get(e.currentTarget).index;
this.draggingItem = { source: index, destination: index };
e.dataTransfer?.setDragImage(DragGhostImage.instance, 0, 0);
e.dataTransfer.effectAllowed = 'move';
this.host.requestUpdate();
}
handleDropOver(e) {
if (this.draggingItem) {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
this.draggingItem.destination = this.items.get(e.currentTarget).index;
this.host.requestUpdate();
}
}
handleDrop(e) {
e.preventDefault();
if (!this.draggingItem) {
return;
}
this.handleReorder(this.draggingItem.source, this.draggingItem.destination);
this.draggingItem = undefined;
this.host.requestUpdate();
}
handleDragEnd(e) {
e;
this.draggingItem = undefined;
this.host.requestUpdate();
}
handleReorder(source, destination) {
this.options.handleReorder?.(source, destination);
}
}
export class DataGridReorderabilityController extends ReorderabilityController {
constructor(host) {
super(host, {});
this.host = host;
}
get enabled() {
return this.host.reorderability
&& this.host.sortingController.enabled === false
&& this.host.detailsController.hasDetails === false;
}
reorder(source, destination) {
this.handleReorder(source, destination);
}
handleReorder(source, destination) {
if (source === destination) {
return;
}
const d = [...this.host.data];
const [movedItem] = d.splice(source, 1);
d.splice(destination, 0, movedItem);
this.host.data = d;
const isMovingDown = source < destination;
this.host.reorder.dispatch([
{
record: this.host.dataRecords[destination],
oldIndex: source,
type: 'move',
},
...Array.from({ length: Math.abs(destination - source) })
.map((_, i) => isMovingDown ? source + i : destination + i + 1)
.map(i => ({
record: this.host.dataRecords[i],
oldIndex: isMovingDown ? i + 1 : i - 1,
type: 'shift',
}))
]);
}
}