Skip to content
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

Add DnD for mobile #699

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions drag/drag.view.tree
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ $mol_drag $mol_ghost
dragstart?event <=> drag_start?event <=> start?event null
drag?event <=> drag_move?event <=> move?event null
dragend?event <=> drag_end?event <=> end?event null
touchstart?event <=> drag_start?event <=> start?event null
touchmove?event <=> drag_move?event <=> move?event null
touchend?event <=> drag_end?event <=> end?event null
attr *
draggable true
mol_drag_status <= status? \ready
Expand Down
220 changes: 180 additions & 40 deletions drag/drag.view.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,181 @@
namespace $.$$ {

/**
* @see https://mol.hyoo.ru/#!section=demos/demo=mol_drag_demo
*/
export class $mol_drag extends $.$mol_drag {

@ $mol_mem
status( next = 'ready' as 'ready' | 'drag' ) { return next }

drag_start( event : DragEvent ) {

setTimeout( ()=> this.status( 'drag' ) )

const transfer = this.transfer()
for( let type in transfer ) {
event.dataTransfer!.setData( type , transfer[ type as keyof typeof transfer] )
}

event.dataTransfer!.setDragImage( this.image() , 0 , -32 )

const effects = [] as string[]
if( this.allow_copy() ) effects.push( 'Copy' )
if( this.allow_link() ) effects.push( 'Link' )
if( this.allow_move() ) effects.push( 'Move' )

let effectAllowed = effects[0].toLowerCase() + effects.slice(1).join('')
if( effectAllowed === 'copyLinkMove' ) effectAllowed = 'all'
event.dataTransfer!.effectAllowed = effectAllowed as DataTransfer['effectAllowed']

this.start( event )

}

drag_end( event : DragEvent ) {
setTimeout( ()=> this.status( 'ready' ) )
this.end( event )
}

}
}
/**
* @see https://mol.hyoo.ru/#!section=demos/demo=mol_drag_demo
*/
export class $mol_drag extends $.$mol_drag {
private dragged_task: HTMLElement | null = null;
private initial_task_index: number | null = null;
private draggedOverElem: HTMLElement | null = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be no info about the dragged element, this is the work for $mol_drop. The new position of the passed element must be determined by $mol_drop. $mol_drag don't move element before drop.

If we want a component that immediately changes the position of elements when the pointer is moved, it will be a different component, not $mol_drag.

private dragImageElem: HTMLElement | null = null;

@$mol_mem
status(next = "ready" as "ready" | "drag") {
return next;
}

drag_start(event: DragEvent | TouchEvent) {
if (event instanceof TouchEvent) {
this.handle_touch_start(event);
return;
}

setTimeout(() => this.status("drag"));

this.dragged_task = event.target as HTMLElement;
this.initial_task_index = Array.from(
this.dragged_task.parentElement!.children
).indexOf(this.dragged_task);

const transfer = this.transfer();
for (let type in transfer) {
(event as DragEvent).dataTransfer!.setData(
type,
transfer[type as keyof typeof transfer]
);
}

(event as DragEvent).dataTransfer!.setDragImage(this.image(), 0, -32);

const effects = [] as string[];
if (this.allow_copy()) effects.push("Copy");
if (this.allow_link()) effects.push("Link");
if (this.allow_move()) effects.push("Move");

let effectAllowed = effects[0].toLowerCase() + effects.slice(1).join("");
if (effectAllowed === "copyLinkMove") effectAllowed = "all";
(event as DragEvent).dataTransfer!.effectAllowed =
effectAllowed as DataTransfer["effectAllowed"];

this.start(event as DragEvent);
}

drag_move(event: DragEvent | TouchEvent) {
if (event instanceof TouchEvent) {
this.handle_touch_move(event);
return;
}

event.preventDefault();

const draggedOverElem = document.elementFromPoint(
event.clientX,
event.clientY
);
if (
draggedOverElem &&
draggedOverElem !== this.dragged_task &&
draggedOverElem.parentElement === this.dragged_task!.parentElement
) {
const parent = this.dragged_task!.parentElement;
const draggedOverIndex = Array.from(parent!.children).indexOf(
draggedOverElem as HTMLElement
);

if (draggedOverIndex !== this.initial_task_index) {
parent!.insertBefore(this.dragged_task!, draggedOverElem);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't make any direct changes to the DOM. Instead, we set values ​​for props and the components render the DOM.

this.initial_task_index = draggedOverIndex;
}
}
}

drag_end(event: DragEvent | TouchEvent) {
if (event instanceof TouchEvent) {
this.handle_touch_end(event);
return;
}

setTimeout(() => this.status("ready"));

this.dragged_task = null;
this.initial_task_index = null;

this.end(event as DragEvent);
}

handle_touch_start(event: TouchEvent) {
event.preventDefault();
this.status("drag");

this.dragged_task = event.target as HTMLElement;
this.initial_task_index = Array.from(
this.dragged_task!.parentElement!.children
).indexOf(this.dragged_task);

this.dragImageElem = this.dragged_task.cloneNode(true) as HTMLElement;

this.dragImageElem.style.position = "absolute";
this.dragImageElem.style.pointerEvents = "none";
this.dragImageElem.style.zIndex = "9999";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dragImageElem should be located in drag.view.tree.
Static styles of it in drag.view.css.ts, dynamic styles in view.tree style *
Name should be in snake_case.

Also I suggest not to clone the dragged element, it may be large and this may cause glitches since the cloned elements will have the same ids.
Instead I suggest making Drag_label $mol_paragraph which will just be the title of the dragged element

this.dragImageElem.style.color = "var(--mol_theme_focus)";

document.body.appendChild(this.dragImageElem);

const touch = event.touches[0];
this.moveDragImage(touch.clientX, touch.clientY);

this.start(event);
}

handle_touch_move(event: TouchEvent) {
event.preventDefault();

const touch = event.touches[0];
const draggedOverElem = document.elementFromPoint(
touch.clientX,
touch.clientY
);

if (this.dragImageElem) {
this.moveDragImage(touch.clientX, touch.clientY);
}

this.draggedOverElem = draggedOverElem as HTMLElement;

if (
draggedOverElem &&
draggedOverElem !== this.dragged_task &&
draggedOverElem.parentElement === this.dragged_task!.parentElement
) {
const parent = this.dragged_task!.parentElement;
const draggedOverIndex = Array.from(parent!.children).indexOf(
draggedOverElem as HTMLElement
);

if (draggedOverIndex !== this.initial_task_index) {
parent!.insertBefore(this.dragged_task!, draggedOverElem);
this.initial_task_index = draggedOverIndex;
}
}
}

handle_touch_end(event: TouchEvent) {
if (this.draggedOverElem) {
if (
this.draggedOverElem.id ==
"$hyoo_mol.Root(0).Demos().Widget('$mol_drag_demo').Trash()"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will only work for $hyoo_mol.Root(0).Demos().Widget('$mol_drag_demo').Trash().

Anyway, this should be handled in $mol_drop, not $mol_drag.

) {
this.dragged_task?.remove();
}
}

if (this.dragImageElem) {
this.dragImageElem.remove();
this.dragImageElem = null;
}

this.draggedOverElem = null;
this.dragged_task = null;
this.initial_task_index = null;
this.status("ready");
this.end(event);
}

moveDragImage(x: number, y: number) {
if (this.dragImageElem) {
this.dragImageElem.style.left = `${x + 50}px`;
this.dragImageElem.style.top = `${y + 50}px`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-50, -50 would be better, I can't see the object under my finger :)

}
}
}
}