Project

General

Profile

1
function plugin(UIkit) {
2

    
3
    if (plugin.installed) {
4
        return;
5
    }
6

    
7
    var { mixin, util } = UIkit;
8
    var {$, docElement: doc, extend, getDimensions, isWithin, on, off, offsetTop, pointerDown, pointerMove, pointerUp, promise, win} = util;
9

    
10
    UIkit.component('sortable', {
11

    
12
        mixins: [mixin.class],
13

    
14
        props: {
15
            group: String,
16
            animation: Number,
17
            threshold: Number,
18
            clsItem: String,
19
            clsPlaceholder: String,
20
            clsDrag: String,
21
            clsDragState: String,
22
            clsBase: String,
23
            clsNoDrag: String,
24
            clsEmpty: String,
25
            clsCustom: String,
26
            handle: String
27
        },
28

    
29
        defaults: {
30
            group: false,
31
            animation: 150,
32
            threshold: 5,
33
            clsItem: 'uk-sortable-item',
34
            clsPlaceholder: 'uk-sortable-placeholder',
35
            clsDrag: 'uk-sortable-drag',
36
            clsDragState: 'uk-drag',
37
            clsBase: 'uk-sortable',
38
            clsNoDrag: 'uk-sortable-nodrag',
39
            clsEmpty: 'uk-sortable-empty',
40
            clsCustom: '',
41
            handle: false
42
        },
43

    
44
        init() {
45
            ['init', 'start', 'move', 'end'].forEach(key => {
46
                let fn = this[key];
47
                this[key] = e => {
48
                    e = e.originalEvent || e;
49
                    this.scrollY = window.scrollY;
50
                    var {pageX, pageY} = e.touches && e.touches[0] || e;
51
                    this.pos = {x: pageX, y: pageY};
52

    
53
                    fn(e);
54
                }
55
            });
56
        },
57

    
58
        events: {
59

    
60
            [pointerDown]: 'init'
61

    
62
        },
63

    
64
        update: {
65

    
66
            write() {
67

    
68
                if (this.clsEmpty) {
69
                    this.$el.toggleClass(this.clsEmpty, !this.$el.children().length);
70
                }
71

    
72
                if (!this.drag) {
73
                    return;
74
                }
75

    
76
                this.drag.offset({top: this.pos.y + this.origin.top, left: this.pos.x + this.origin.left});
77

    
78
                var top = offsetTop(this.drag), bottom = top + this.drag[0].offsetHeight;
79

    
80
                if (top > 0 && top < this.scrollY) {
81
                    setTimeout(() => win.scrollTop(this.scrollY - 5), 5);
82
                } else if (bottom < doc[0].offsetHeight && bottom > window.innerHeight + this.scrollY) {
83
                    setTimeout(() => win.scrollTop(this.scrollY + 5), 5);
84
                }
85

    
86
            }
87

    
88
        },
89

    
90
        methods: {
91

    
92
            init(e) {
93

    
94
                var target = $(e.target), placeholder = this.$el.children().filter((i, el) => isWithin(e.target, el));
95

    
96
                if (!placeholder.length
97
                    || target.is(':input')
98
                    || this.handle && !isWithin(target, this.handle)
99
                    || e.button && e.button !== 0
100
                    || isWithin(target, `.${this.clsNoDrag}`)
101
                ) {
102
                    return;
103
                }
104

    
105
                e.preventDefault();
106
                e.stopPropagation();
107

    
108
                this.touched = [this];
109
                this.placeholder = placeholder;
110
                this.origin = extend({target, index: this.placeholder.index()}, this.pos);
111

    
112
                doc.on(pointerMove, this.move);
113
                doc.on(pointerUp, this.end);
114
                win.on('scroll', this.scroll);
115

    
116
                if (!this.threshold) {
117
                    this.start(e);
118
                }
119

    
120
            },
121

    
122
            start(e) {
123

    
124
                this.drag = $(this.placeholder[0].outerHTML.replace(/^<li/i, '<div').replace(/li>$/i, 'div>'))
125
                    .attr('uk-no-boot', '')
126
                    .addClass(`${this.clsDrag} ${this.clsCustom}`)
127
                    .css({
128
                        boxSizing: 'border-box',
129
                        width: this.placeholder.outerWidth(),
130
                        height: this.placeholder.outerHeight()
131
                    })
132
                    .css(this.placeholder.css(['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom']))
133
                    .appendTo(UIkit.container);
134

    
135
                this.drag.children().first().height(this.placeholder.children().height());
136

    
137
                var {left, top} = getDimensions(this.placeholder);
138
                extend(this.origin, {left: left - this.pos.x, top: top - this.pos.y});
139

    
140
                this.placeholder.addClass(this.clsPlaceholder);
141
                this.$el.children().addClass(this.clsItem);
142
                doc.addClass(this.clsDragState);
143

    
144
                this.$el.trigger('start', [this, this.placeholder, this.drag]);
145

    
146
                this.move(e);
147
            },
148

    
149
            move(e) {
150

    
151
                if (!this.drag) {
152

    
153
                    if (Math.abs(this.pos.x - this.origin.x) > this.threshold || Math.abs(this.pos.y - this.origin.y) > this.threshold) {
154
                        this.start(e);
155
                    }
156

    
157
                    return;
158
                }
159

    
160
                this.$emit();
161

    
162
                var target = e.type === 'mousemove' ? e.target : document.elementFromPoint(this.pos.x - document.body.scrollLeft, this.pos.y - document.body.scrollTop),
163
                    sortable = getSortable(target),
164
                    previous = getSortable(this.placeholder[0]),
165
                    move = sortable !== previous;
166

    
167
                if (!sortable || isWithin(target, this.placeholder) || move && (!sortable.group || sortable.group !== previous.group)) {
168
                    return;
169
                }
170

    
171
                target = sortable.$el.is(target.parentNode) && $(target) || sortable.$el.children().has(target);
172

    
173
                if (move) {
174
                    previous.remove(this.placeholder);
175
                } else if (!target.length) {
176
                    return;
177
                }
178

    
179
                sortable.insert(this.placeholder, target);
180

    
181
                if (!~this.touched.indexOf(sortable)) {
182
                    this.touched.push(sortable);
183
                }
184

    
185
            },
186

    
187
            scroll() {
188
                var scroll = window.scrollY;
189
                if (scroll !== this.scrollY) {
190
                    this.pos.y += scroll - this.scrollY;
191
                    this.scrollY = scroll;
192
                    this.$emit();
193
                }
194
            },
195

    
196
            end(e) {
197

    
198
                doc.off(pointerMove, this.move);
199
                doc.off(pointerUp, this.end);
200
                win.off('scroll', this.scroll);
201

    
202
                if (!this.drag) {
203

    
204
                    if (e.type !== 'mouseup' && isWithin(e.target, 'a[href]')) {
205
                        location.href = $(e.target).closest('a[href]').attr('href');
206
                    }
207

    
208
                    return;
209
                }
210

    
211
                preventClick();
212

    
213
                var sortable = getSortable(this.placeholder[0]);
214

    
215
                if (this === sortable) {
216
                    if (this.origin.index !== this.placeholder.index()) {
217
                        this.$el.trigger('change', [this, this.placeholder, 'moved']);
218
                    }
219
                } else {
220
                    sortable.$el.trigger('change', [sortable, this.placeholder, 'added']);
221
                    this.$el.trigger('change', [this, this.placeholder, 'removed']);
222
                }
223

    
224
                this.$el.trigger('stop', [this]);
225

    
226
                this.drag.remove();
227
                this.drag = null;
228

    
229
                this.touched.forEach(sortable => sortable.$el.children().removeClass(`${sortable.clsPlaceholder} ${sortable.clsItem}`));
230

    
231
                doc.removeClass(this.clsDragState);
232

    
233
            },
234

    
235
            insert(element, target) {
236

    
237
                this.$el.children().addClass(this.clsItem);
238

    
239
                var insert = () => {
240

    
241
                    if (target.length) {
242

    
243
                        if (!this.$el.has(element).length || element.prevAll().filter(target).length) {
244
                            element.insertBefore(target);
245
                        } else {
246
                            element.insertAfter(target);
247
                        }
248

    
249
                    } else {
250
                        this.$el.append(element);
251
                    }
252

    
253
                };
254

    
255
                if (this.animation) {
256
                    this.animate(insert);
257
                } else {
258
                    insert();
259
                }
260

    
261
            },
262

    
263
            remove(element) {
264

    
265
                if (!this.$el.has(element).length) {
266
                    return;
267
                }
268

    
269
                if (this.animation) {
270
                    this.animate(() => element.detach());
271
                } else {
272
                    element.detach();
273
                }
274

    
275
            },
276

    
277
            animate(action) {
278

    
279
                var props = [],
280
                    children = this.$el.children().toArray().map(el => {
281
                        el = $(el);
282
                        props.push(extend({
283
                            position: 'absolute',
284
                            pointerEvents: 'none',
285
                            width: el.outerWidth(),
286
                            height: el.outerHeight()
287
                        }, el.position()));
288
                        return el;
289
                    }),
290
                    reset = {position: '', width: '', height: '', pointerEvents: '', top: '', left: ''};
291

    
292
                action();
293

    
294
                children.forEach(el => el.stop());
295
                this.$el.children().css(reset);
296
                this.$updateSync('update', true);
297

    
298
                this.$el.css('min-height', this.$el.height());
299

    
300
                var positions = children.map(el => el.position());
301
                promise.all(children.map((el, i) => el.css(props[i]).animate(positions[i], this.animation).promise()))
302
                    .then(() => {
303
                        this.$el.css('min-height', '').children().css(reset);
304
                        this.$updateSync('update', true);
305
                    });
306

    
307
            }
308

    
309
        }
310

    
311
    });
312

    
313
    function getSortable(element) {
314
        return UIkit.getComponent(element, 'sortable') || element.parentNode && getSortable(element.parentNode);
315
    }
316

    
317
    function preventClick() {
318
        var timer = setTimeout(() => doc.trigger('click'), 0),
319
            listener = e => {
320

    
321
                e.preventDefault();
322
                e.stopPropagation();
323

    
324
                clearTimeout(timer);
325
                off(doc, 'click', listener, true);
326
            };
327

    
328
        on(doc, 'click', listener, true);
329
    }
330

    
331
}
332

    
333
if (!BUNDLED && typeof window !== 'undefined' && window.UIkit) {
334
    window.UIkit.use(plugin);
335
}
336

    
337
export default plugin;
(3-3/5)