@lumjs/web-draggable-list 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +266 -19
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @lumjs/web-draggable-list
|
|
3
|
+
*/
|
|
1
4
|
import { enableDragDropTouch } from '@dragdroptouch/drag-drop-touch';
|
|
2
5
|
import WC from '@lumjs/web-core';
|
|
6
|
+
import core from '@lumjs/core';
|
|
3
7
|
|
|
4
|
-
export {WC};
|
|
8
|
+
export { WC };
|
|
5
9
|
export const { indexOf, on } = WC.ez;
|
|
6
10
|
|
|
11
|
+
const { isObj } = core.types;
|
|
12
|
+
|
|
7
13
|
const DEF_OPTS = {
|
|
8
|
-
|
|
14
|
+
autoRegister: null,
|
|
15
|
+
delegated: true,
|
|
16
|
+
enableTouch: true,
|
|
17
|
+
itemMatch: 'li',
|
|
18
|
+
itemSelector: '& > li',
|
|
9
19
|
onDrop() {},
|
|
10
20
|
onEnd() {},
|
|
11
21
|
onEnter() {},
|
|
@@ -24,9 +34,98 @@ const DRAG_METHODS = [
|
|
|
24
34
|
'handleDragEnd',
|
|
25
35
|
]
|
|
26
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A class for making it so you can move list items using
|
|
39
|
+
* drag and drop. Includes touch support by default.
|
|
40
|
+
*/
|
|
27
41
|
export class DraggableList {
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Build a DraggableList instance.
|
|
44
|
+
*
|
|
45
|
+
* @param {(string|Element)} listElem - Parent element for the list.
|
|
46
|
+
*
|
|
47
|
+
* Usually this should be the container (e.g. `<ul>`) that is the direct
|
|
48
|
+
* parent of the list items, and most of the option defaults expect that.
|
|
49
|
+
*
|
|
50
|
+
* If you use an element above the container here (say for instance if you
|
|
51
|
+
* have multiple lists that you want to be controlled by the same
|
|
52
|
+
* DraggableList instance for reasons left to your own devices), you will
|
|
53
|
+
* need to adjust at least `opts.itemSelector` to reflect that.
|
|
54
|
+
*
|
|
55
|
+
* If this is a string, it will be used as a query selector on the top-level
|
|
56
|
+
* document to find the desired element.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} [opts] Options.
|
|
59
|
+
*
|
|
60
|
+
* You may specify this argument more than once. If the same options are
|
|
61
|
+
* set in multiple opts arguments, the ones specified last will be used.
|
|
62
|
+
* This design was mostly just to make *presets* easier to use.
|
|
63
|
+
*
|
|
64
|
+
* In addition to the options listed here, some presets may add their own.
|
|
65
|
+
*
|
|
66
|
+
* @param {?boolean} [opts.autoRegister=null] Register items automatically?
|
|
67
|
+
*
|
|
68
|
+
* If this is true then makeDraggable() will be called with no arguments
|
|
69
|
+
* at the very end of the constructor. If this is false, it is left up to
|
|
70
|
+
* you to call it explicitly (if its needed at all) from your app's code.
|
|
71
|
+
*
|
|
72
|
+
* If this is null (the default value), then its value will be determined
|
|
73
|
+
* using: `!opts.delegated`; which I think is a decent default.
|
|
74
|
+
*
|
|
75
|
+
* @param {boolean} [opts.delegated=true] Use delegated events?
|
|
76
|
+
*
|
|
77
|
+
* If this is true (the default), then the event listeners will be
|
|
78
|
+
* registered on the `listElem` using delegation to dispatch to the
|
|
79
|
+
* appropriate item elements.
|
|
80
|
+
*
|
|
81
|
+
* @param {boolean} [opts.enableTouch=true] Enable Touch compatibility?
|
|
82
|
+
*
|
|
83
|
+
* If this is true (the default), then we will call enableDragDropTouch(),
|
|
84
|
+
* passing the `listElem` as the first two arguments, and `opts` as the last.
|
|
85
|
+
*
|
|
86
|
+
* See https://github.com/drag-drop-touch-js/dragdroptouch for details on
|
|
87
|
+
* that function. Any of its options may be specified in `opts` as well.
|
|
88
|
+
*
|
|
89
|
+
* If you have custom requirements of any kind, you can set this to
|
|
90
|
+
* false and you'll be responsible for setting up DragDropTouch yourself.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} [opts.itemMatch='li'] Delegation selector for items.
|
|
93
|
+
*
|
|
94
|
+
* This will be used to find matching list items using `element.match()`.
|
|
95
|
+
* The default `'li'` assumes you are using a <ul>, <ol>, or <menu> for
|
|
96
|
+
* your list container element.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} [opts.itemSelector='& > li'] Selector to find list items.
|
|
99
|
+
*
|
|
100
|
+
* This is only ever used by the `listItems` accessor property to find
|
|
101
|
+
* list items using `listElem.querySelectorAll(opts.itemSelector)`.
|
|
102
|
+
*
|
|
103
|
+
* The only time this would be used by the class itself is if you
|
|
104
|
+
* call makeDraggable() without specifying any elements manually,
|
|
105
|
+
* which is the case if `opts.autoRegister` was enabled.
|
|
106
|
+
*
|
|
107
|
+
* The default `'& > li'` only matches <li> elements that are *direct*
|
|
108
|
+
* children of the listElem itself. So if that is not the case, you will
|
|
109
|
+
* need to adjust this option.
|
|
110
|
+
*
|
|
111
|
+
* @param {DropEventHandler} [opts.onDrop] Handler for `drop` events.
|
|
112
|
+
*
|
|
113
|
+
* The DragEvent object passed to this handler has two extra properties
|
|
114
|
+
* added; one representing the original position of the dragged element,
|
|
115
|
+
* and another for the position it was moved to after dropping it.
|
|
116
|
+
*
|
|
117
|
+
* Of all the handlers, this one is the one you'll likely want to define
|
|
118
|
+
* yourself and do something with.
|
|
119
|
+
*
|
|
120
|
+
* @param {DragEventHandler} [opts.onEnd] Handler for `dragend` events.
|
|
121
|
+
* @param {DragEventHandler} [opts.onEnter] Handler for `dragenter` events.
|
|
122
|
+
* @param {DragEventHandler} [opts.onLeave] Handler for `dragleave` events.
|
|
123
|
+
* @param {DragEventHandler} [opts.onOver] Handler for `dragover` events.
|
|
124
|
+
* @param {DragEventHandler} [opts.onStart] Handler for `dragstart` events.
|
|
125
|
+
* @param {boolean} [opts.verbose=true] Enable verbose debugging info.
|
|
126
|
+
*/
|
|
127
|
+
constructor(listElem, ...opts) {
|
|
128
|
+
this.opts = opts = Object.assign({}, DEF_OPTS, ...opts);
|
|
30
129
|
if (typeof listElem === 'string') {
|
|
31
130
|
listElem = document.querySelector(listElem);
|
|
32
131
|
}
|
|
@@ -37,18 +136,106 @@ export class DraggableList {
|
|
|
37
136
|
|
|
38
137
|
this.listElem = listElem;
|
|
39
138
|
this.dragging = null;
|
|
40
|
-
|
|
139
|
+
this.registration = new Map();
|
|
140
|
+
|
|
141
|
+
if (opts.enableTouch) {
|
|
142
|
+
enableDragDropTouch(listElem, listElem, opts);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (opts.delegated) { // Delegated event registration is the best IMHO!
|
|
146
|
+
this.registerDelegation();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (opts.autoRegister ?? !opts.delegated) {
|
|
150
|
+
this.makeDraggable();
|
|
151
|
+
}
|
|
41
152
|
}
|
|
42
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Accessor to get a NodeList containing the list items.
|
|
156
|
+
*/
|
|
43
157
|
get listItems() {
|
|
44
|
-
return this.listElem.querySelectorAll(this.opts.
|
|
158
|
+
return this.listElem.querySelectorAll(this.opts.itemSelector);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The method used to register delegated event listeners.
|
|
163
|
+
*
|
|
164
|
+
* You shouldn't normally have to call this manually, however
|
|
165
|
+
* you can use it to remove the delegated event listeners if you
|
|
166
|
+
* want to disable the drag and drop code for some reason, you
|
|
167
|
+
* can use this to do so.
|
|
168
|
+
*
|
|
169
|
+
* @param {boolean} [enabled=true] Enable delegated events?
|
|
170
|
+
*
|
|
171
|
+
* If true the events will be registered. If false, they will
|
|
172
|
+
* be removed. You can use this to toggle draggability on and off.
|
|
173
|
+
*
|
|
174
|
+
* @returns {DraggableList} this instance.
|
|
175
|
+
*/
|
|
176
|
+
registerDelegation(enabled=true) {
|
|
177
|
+
let elem = this.listElem;
|
|
178
|
+
let opts = this.opts;
|
|
179
|
+
let registry = this.registration.get(elem) ?? {};
|
|
180
|
+
|
|
181
|
+
for (let meth of DRAG_METHODS)
|
|
182
|
+
{
|
|
183
|
+
let eventName = meth.replace('handle','').toLowerCase();
|
|
184
|
+
|
|
185
|
+
if (isObj(registry[eventName])) {
|
|
186
|
+
// Unregister an existing event handler.
|
|
187
|
+
registry[eventName].off();
|
|
188
|
+
delete registry[eventName];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (enabled) {
|
|
192
|
+
registry[eventName] = on(elem, eventName, opts.itemMatch, ev => {
|
|
193
|
+
if (opts.verbose) {
|
|
194
|
+
console.debug('delegated',
|
|
195
|
+
{eventName, meth, event: ev, opts, list: this});
|
|
196
|
+
}
|
|
197
|
+
this[meth](ev);
|
|
198
|
+
}, {off: true});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.registration.set(elem, registry);
|
|
203
|
+
|
|
204
|
+
return this;
|
|
45
205
|
}
|
|
46
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Make one or more items draggable.
|
|
209
|
+
*
|
|
210
|
+
* If you are using event delegation and your item elements already have the
|
|
211
|
+
* `draggable` attribute set, you shouldn't have to call this at all.
|
|
212
|
+
*
|
|
213
|
+
* If you are **NOT** using delegation this method will have to be called
|
|
214
|
+
* for every item you want to be able to be dragged!
|
|
215
|
+
*
|
|
216
|
+
* @param {boolean} [enabled=true] Make the items draggable?
|
|
217
|
+
*
|
|
218
|
+
* The string of this value will be used to set the `draggable` attribute
|
|
219
|
+
* on each of the items.
|
|
220
|
+
*
|
|
221
|
+
* If `this.opts.delegated` is false then this argument will determine
|
|
222
|
+
* if direct event handlers should be added or removed from each item.
|
|
223
|
+
*
|
|
224
|
+
* @param {...(Element|string)} [elems] Item elements to register.
|
|
225
|
+
*
|
|
226
|
+
* This can be used if you are adding dynamic items.
|
|
227
|
+
*
|
|
228
|
+
* If you don't pass any arguments here then `this.listItems` will be
|
|
229
|
+
* used to find the items elements.
|
|
230
|
+
*
|
|
231
|
+
* @returns {DraggableList} this instance.
|
|
232
|
+
*/
|
|
47
233
|
makeDraggable(enabled=true, ...elems) {
|
|
48
234
|
if (elems.length === 0) {
|
|
49
235
|
elems = this.listItems;
|
|
50
236
|
}
|
|
51
237
|
|
|
238
|
+
let delegated = this.opts.delegated;
|
|
52
239
|
let verbose = this.opts.verbose;
|
|
53
240
|
|
|
54
241
|
for (let elem of elems) {
|
|
@@ -62,21 +249,32 @@ export class DraggableList {
|
|
|
62
249
|
|
|
63
250
|
elem.setAttribute('draggable', enabled.toString());
|
|
64
251
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
252
|
+
if (!delegated) {
|
|
253
|
+
let registry = this.registration.get(elem) ?? {};
|
|
254
|
+
|
|
255
|
+
for (let meth of DRAG_METHODS)
|
|
256
|
+
{
|
|
257
|
+
let eventName = meth.replace('handle','').toLowerCase();
|
|
258
|
+
|
|
259
|
+
if (isObj(registry[eventName])) {
|
|
260
|
+
// Unregister an existing event handler.
|
|
261
|
+
registry[eventName].off();
|
|
262
|
+
delete registry[eventName];
|
|
76
263
|
}
|
|
77
|
-
|
|
78
|
-
|
|
264
|
+
|
|
265
|
+
if (enabled) { // Register an event handler.
|
|
266
|
+
registry[eventName] = on(elem, eventName, ev => {
|
|
267
|
+
if (verbose) {
|
|
268
|
+
console.debug('direct',
|
|
269
|
+
{eventName, meth, event: ev, elem, list: this});
|
|
270
|
+
}
|
|
271
|
+
this[meth](ev);
|
|
272
|
+
}, {off: true});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
this.registration.set(elem, registry);
|
|
79
276
|
}
|
|
277
|
+
|
|
80
278
|
}
|
|
81
279
|
|
|
82
280
|
return this;
|
|
@@ -144,10 +342,58 @@ export class DraggableList {
|
|
|
144
342
|
|
|
145
343
|
export default DraggableList;
|
|
146
344
|
|
|
345
|
+
/**
|
|
346
|
+
* A Preset that adds a CSS class to list items when another items is being
|
|
347
|
+
* dragged over it. It provides `onEnter`, `onLeave`, and `onEnd` handlers.
|
|
348
|
+
*
|
|
349
|
+
* To set the CSS class, it adds an extra option called `dragOverClass`,
|
|
350
|
+
* which is a string, and defaults to `'drag-over'`.
|
|
351
|
+
*
|
|
352
|
+
* If you want to use a Preset but also add your own functionality,
|
|
353
|
+
* you can chain handlers by using the call() method. For example:
|
|
354
|
+
*
|
|
355
|
+
* ```js
|
|
356
|
+
* import {DraggableList, ClassyPreset} from '@lumjs/web-draggable-list';
|
|
357
|
+
*
|
|
358
|
+
* // A test to see if the element is NOT a drop target.
|
|
359
|
+
* const noDrop = event => event.target.classList.has('no-drop');
|
|
360
|
+
*
|
|
361
|
+
* let dlist = new DraggableList('#my-list', ClassyPreset, {
|
|
362
|
+
* dragOverClass: 'drop-target',
|
|
363
|
+
* onEnter(event) {
|
|
364
|
+
* if (noDrop(event)) return;
|
|
365
|
+
* ClassyPreset.onEnter.call(this, event);
|
|
366
|
+
* },
|
|
367
|
+
* onDrop(event) {
|
|
368
|
+
* if (noDrop(event)) return;
|
|
369
|
+
* myApp.itemDropped(event)
|
|
370
|
+
* },
|
|
371
|
+
* });
|
|
372
|
+
* ```
|
|
373
|
+
* That example is rather limited, as you'd also want to change the dropEffect
|
|
374
|
+
* in the `onOver()` handler, but anyway, hopefully it gives you an idea.
|
|
375
|
+
*
|
|
376
|
+
* See `demo/main.src.js` for a more realistic example.
|
|
377
|
+
*/
|
|
378
|
+
export const ClassyPreset = {
|
|
379
|
+
dragOverClass: 'drag-over',
|
|
380
|
+
onEnter(ev) {
|
|
381
|
+
ev.target.classList.add(this.opts.dragOverClass);
|
|
382
|
+
},
|
|
383
|
+
onLeave(ev) {
|
|
384
|
+
ev.target.classList.remove(this.opts.dragOverClass);
|
|
385
|
+
},
|
|
386
|
+
onEnd() {
|
|
387
|
+
let doclass = this.opts.dragOverClass;
|
|
388
|
+
this.listItems.forEach(item => item.classList.remove(doclass));
|
|
389
|
+
},
|
|
390
|
+
}
|
|
391
|
+
|
|
147
392
|
/**
|
|
148
393
|
* A Drag Event handler
|
|
149
394
|
* @callback DragEventHandler
|
|
150
395
|
* @param {DragEvent} event - The event being handled.
|
|
396
|
+
* @this {DraggableList}
|
|
151
397
|
*/
|
|
152
398
|
|
|
153
399
|
/**
|
|
@@ -156,4 +402,5 @@ export default DraggableList;
|
|
|
156
402
|
* @param {DragEvent} event - The event being handled; with extra metadata.
|
|
157
403
|
* @param {number} event.oldpos - The original position of the dragged item.
|
|
158
404
|
* @param {number} event.newpso - The new position of the dragged item.
|
|
405
|
+
* @this {DraggableList}
|
|
159
406
|
*/
|