Skip to content

Commit 17b7d5b

Browse files
ntilwallijvanbruegge
authored andcommitted
Fix mousemove bug, ghost offset bug, requiring children if no handle option set, remove need for emitBetween, use border-box, add code to implement ghostClass options
1 parent 2897020 commit 17b7d5b

File tree

9 files changed

+194
-106
lines changed

9 files changed

+194
-106
lines changed

examples/horizontal/css/main.css

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
font-size: 35px;
33
}
44

5+
html {
6+
box-sizing: border-box;
7+
}
8+
*, *:before, *:after {
9+
box-sizing: inherit;
10+
}
11+
512
body, html {
613
max-height: 100%;
714
margin: 0;
@@ -13,6 +20,7 @@ body {
1320

1421
ul {
1522
margin: 0;
23+
position: relative;
1624
}
1725

1826
li {

examples/simple/css/main.css

+14-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
font-size: 35px;
33
}
44

5+
html {
6+
box-sizing: border-box;
7+
}
8+
*, *:before, *:after {
9+
box-sizing: inherit;
10+
}
11+
512
body, html {
613
max-height: 100%;
714
margin: 0;
@@ -13,13 +20,18 @@ body {
1320

1421
ul {
1522
margin: 0;
23+
position: relative;
1624
}
1725

1826
li {
19-
padding: 5px;
27+
/*padding: 5px;*/
2028
background: lightgray;
2129
}
2230

2331
li + li {
24-
margin-top: 40px;
32+
/*margin-top: 5px;*/
33+
}
34+
35+
.ghost {
36+
border: 1px solid black;
2537
}

examples/simple/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function main({ DOM } : Sources) : Sinks
2424
li('.li', '', ['Option 6']),
2525
])
2626
)
27-
.compose(makeSortable<Stream<VNode>>(DOM));
27+
.compose(makeSortable<Stream<VNode>>(DOM, { ghostClass: '.ghost', selectionDelay: 500 }));
2828

2929
return {
3030
DOM: vdom$

src/definitions.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,18 @@ export interface SortableOptions {
2929
/**
3030
* Optional, has to be a CSS class name
3131
* Can be used to style the ghost item
32-
* @default the first CSS class of the first item
32+
* @default string ''
3333
* @type {string}
3434
*/
3535
ghostClass?: string;
36+
37+
/**
38+
* Optional, number of milliseconds to
39+
* wait after mousedown/touchstart before selecting the item
40+
* @default number in milliseconds, defaults to 0
41+
* @type {number}
42+
*/
43+
selectionDelay?: number;
3644
}
3745

3846
/**
@@ -43,6 +51,20 @@ export interface SortableOptions {
4351
export interface MouseOffset {
4452
x: number;
4553
y: number;
54+
itemLeft: number;
55+
itemTop: number;
56+
parentLeft: number;
57+
parentTop: number;
58+
}
59+
60+
/**
61+
* Contains the offset from mousedown position
62+
* distX and distY increase going down and to the left
63+
* @type {StartPositionOffset}
64+
*/
65+
export interface StartPositionOffset extends MouseEvent {
66+
distX: number;
67+
distY: number;
4668
}
4769

4870
/**

src/eventHandlers/mousedown.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { VNode } from '@cycle/dom';
22
import { select } from 'snabbdom-selector';
3-
import { EventHandler, MouseOffset } from '../definitions';
3+
import { EventHandler, MouseOffset, SortableOptions } from '../definitions';
44

55
import {
66
getIndex,
77
getGhostStyle,
88
findParent,
99
addAttributes,
10+
addGhostClass,
1011
replaceNode,
1112
getBodyStyle,
1213
addKeys
@@ -22,17 +23,22 @@ export const mousedownHandler: EventHandler = (node, event, options) => {
2223
options.parentSelector + ' > *'
2324
);
2425
const itemRect: ClientRect = item.getBoundingClientRect();
26+
const parentNode: Element = item.parentElement;
27+
const parentRect: ClientRect = parentNode.getBoundingClientRect();
2528
const mouseOffset: MouseOffset = {
2629
x: itemRect.left - event.clientX,
27-
y: itemRect.top - event.clientY
30+
y: itemRect.top - event.clientY,
31+
itemLeft: itemRect.left,
32+
itemTop: itemRect.top,
33+
parentLeft: parentRect.left,
34+
parentTop: parentRect.top
2835
};
2936

3037
const body: Element = findParent(event.target as Element, 'body');
3138
body.setAttribute('style', getBodyStyle());
3239

33-
const parent: VNode = addKeys(select(options.parentSelector, node)[0]);
40+
const parent: VNode = select(options.parentSelector, node)[0];
3441
const index: number = getIndex(item);
35-
3642
const ghostAttrs: { [name: string]: string } = {
3743
'data-mouseoffset': JSON.stringify(mouseOffset),
3844
'data-itemdimensions': JSON.stringify({
@@ -41,7 +47,7 @@ export const mousedownHandler: EventHandler = (node, event, options) => {
4147
}),
4248
'data-itemindex': index.toString(),
4349
'data-originalIndex': index.toString(),
44-
style: getGhostStyle(event, mouseOffset, item)
50+
style: getGhostStyle(mouseOffset, item)
4551
};
4652

4753
const items: VNode[] = parent.children as VNode[];
@@ -50,7 +56,10 @@ export const mousedownHandler: EventHandler = (node, event, options) => {
5056
...items.slice(0, index),
5157
addAttributes(items[index], { style: 'opacity: 0;' }),
5258
...items.slice(index + 1),
53-
addAttributes({ ...items[index], elm: undefined }, ghostAttrs)
59+
addGhostClass(
60+
addAttributes({ ...items[index], elm: undefined }, ghostAttrs),
61+
options.ghostClass
62+
)
5463
].map((c, i) => addAttributes(c, { 'data-index': i }));
5564

5665
return replaceNode(node, options.parentSelector, { ...parent, children });

src/eventHandlers/mousemove.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { select } from 'snabbdom-selector';
33
import {
44
EventHandler,
55
MouseOffset,
6+
StartPositionOffset,
67
ItemDimensions,
78
Intersection
89
} from '../definitions';
@@ -24,6 +25,14 @@ export const mousemoveHandler: EventHandler = (node, event, options) => {
2425
const parent: VNode = select(options.parentSelector, node)[0];
2526
const ghost: VNode = parent.children[parent.children.length - 1] as VNode;
2627

28+
// Hack for now. Immediately after the mouse down event if the mousemove
29+
// handler gets called (as in the pointer gets moved very quickly) the ghost
30+
// VNode may not have been attached to the element yet causing ghost.elm to
31+
// be undefined, in which case we just return the node unchanged
32+
if (!ghost.elm) {
33+
return node;
34+
}
35+
2736
const mouseOffset: MouseOffset = JSON.parse(
2837
ghost.data.attrs['data-mouseoffset']
2938
);
@@ -56,9 +65,12 @@ export const mousemoveHandler: EventHandler = (node, event, options) => {
5665
: -itemIntersection > maxArea - itemArea
5766
? maxIntersection[1]
5867
: itemIndex;
59-
6068
const ghostAttrs: { [attr: string]: string } = {
61-
style: updateGhostStyle(event, mouseOffset, ghost.elm as Element),
69+
style: updateGhostStyle(
70+
event as StartPositionOffset,
71+
mouseOffset,
72+
ghost.elm as Element
73+
),
6274
'data-itemindex': newIndex.toString()
6375
};
6476

src/helpers.ts

+38-19
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { select, classNameFromVNode } from 'snabbdom-selector';
44
import {
55
SortableOptions,
66
MouseOffset,
7-
ItemDimensions,
7+
StartPositionOffset,
88
Intersection
99
} from './definitions';
1010

@@ -32,7 +32,8 @@ export function applyDefaults(
3232
options.parentSelector ||
3333
'.' + classNameFromVNode(root).split(' ').join('.'),
3434
handle: options.handle || itemSelector,
35-
ghostClass: options.ghostClass || ''
35+
ghostClass: options.ghostClass || '',
36+
selectionDelay: options.selectionDelay || 0
3637
};
3738
}
3839

@@ -124,27 +125,26 @@ export function getIndex(node: any): number {
124125

125126
/**
126127
* Gets the correct style attribute value for the given parameters
127-
* @param {MouseEvent} event the mouse event that was triggered
128+
* @param {StartPositionOffset} event the mouse event that was triggered, enriched with distance information
128129
* @param {MouseOffset} mouseOffset the offset of the item
129130
* @param {ClientRect} itemRect the bounding client rect of the item
130131
* @return {string} the style value
131132
*/
132-
export function getGhostStyle(
133-
event: MouseEvent,
134-
mouseOffset: MouseOffset,
135-
item: Element
136-
): string {
133+
export function getGhostStyle(mouseOffset: MouseOffset, item: Element): string {
137134
const itemRect: ClientRect = item.getBoundingClientRect();
138135
return (
139-
'z-index: 5; margin: 0; pointer-events: none; position: absolute; width: ' +
136+
'z-index: 5; margin: 0; pointer-events: none; position: absolute; ' +
137+
'width: ' +
140138
itemRect.width +
141139
'px; ' +
142140
'height: ' +
143141
itemRect.height +
144-
'px; top: ' +
145-
(event.clientY + mouseOffset.y + window.screenY) +
146-
'px; left: ' +
147-
(event.clientX + mouseOffset.x + window.scrollX) +
142+
'px; ' +
143+
'top: ' +
144+
(mouseOffset.itemTop - mouseOffset.parentTop) +
145+
'px; ' +
146+
'left: ' +
147+
(mouseOffset.itemLeft - mouseOffset.parentLeft) +
148148
'px;'
149149
);
150150
}
@@ -153,7 +153,7 @@ export function getGhostStyle(
153153
* Returns the updated style for this ghost element
154154
*/
155155
export function updateGhostStyle(
156-
event: MouseEvent,
156+
event: StartPositionOffset,
157157
mouseOffset: MouseOffset,
158158
ghost: Element
159159
): string {
@@ -162,9 +162,9 @@ export function updateGhostStyle(
162162
return (
163163
prevStyle.substring(0, prevStyle.indexOf(' top:')) +
164164
' top: ' +
165-
(event.clientY + mouseOffset.y + window.scrollY) +
165+
(mouseOffset.itemTop - mouseOffset.parentTop + event.distY) +
166166
'px; left: ' +
167-
(event.clientX + mouseOffset.x + window.scrollX) +
167+
(mouseOffset.itemLeft - mouseOffset.parentLeft + event.distX) +
168168
'px;'
169169
);
170170
}
@@ -197,21 +197,40 @@ export function findParent(node: Element, selector: string): Element {
197197
}
198198

199199
/**
200-
* Adds the given attribute savely to the VNode
200+
* Adds the given attribute safely to the VNode
201201
* @param {VNode} node the VNode to add the attributes on
202202
* @param {any} addition the new attributes
203203
* @return {VNode} the newly created VNode
204204
*/
205205
export function addAttributes(
206206
e: VNode,
207-
newAttr: { [attr: string]: any }
207+
newAttr: { [attr: string]: any },
208+
ghostClass?: string
208209
): VNode {
209210
const addition: any = {
210211
attrs: Object.assign({}, e.data ? e.data.attrs : undefined, newAttr)
211212
};
212213
return addToData(e, addition);
213214
}
214215

216+
/**
217+
* Adds the given class safely to the VNode
218+
* @param {VNode} node the VNode to add the attributes on
219+
* @param {any} addition the css ghost class
220+
* @return {VNode} the newly created VNode
221+
*/
222+
export function addGhostClass(e: VNode, ghostClass?: string): VNode {
223+
const className =
224+
ghostClass && ghostClass.length > 1
225+
? ghostClass[0] === '.' ? ghostClass.substring(1) : ghostClass
226+
: undefined;
227+
const classVal = className ? { [className]: true } : undefined;
228+
const addition: any = {
229+
class: classVal
230+
};
231+
return addToData(e, addition);
232+
}
233+
215234
/**
216235
* Removes the given attribute from the VNode
217236
* @param {VNode} node the VNode
@@ -233,7 +252,7 @@ export function removeAttribute(node: VNode, attributeName: string): VNode {
233252
}
234253

235254
/**
236-
* Adds the given additions savely to the VNode
255+
* Adds the given additions safely to the VNode
237256
* @param {VNode} node the VNode to add the additions on
238257
* @param {any} addition the new additions
239258
* @return {VNode} the newly created VNode

0 commit comments

Comments
 (0)