Project

General

Profile

1
import { Position, Toggable } from '../mixin/index';
2
import { $, Animation, doc, getDimensions, isWithin, isTouch, MouseTracker, pointerEnter, pointerLeave, query, removeClass } from '../util/index';
3

    
4
export default function (UIkit) {
5

    
6
    var active;
7

    
8
    doc.on('click', e => {
9
        var prev;
10
        while (active && active !== prev && !isWithin(e.target, active.$el) && !(active.toggle && isWithin(e.target, active.toggle.$el))) {
11
            prev = active;
12
            active.hide(false);
13
        }
14
    });
15

    
16
    UIkit.component('drop', {
17

    
18
        mixins: [Position, Toggable],
19

    
20
        args: 'pos',
21

    
22
        props: {
23
            mode: 'list',
24
            toggle: Boolean,
25
            boundary: 'jQuery',
26
            boundaryAlign: Boolean,
27
            delayShow: Number,
28
            delayHide: Number,
29
            clsDrop: String
30
        },
31

    
32
        defaults: {
33
            mode: ['click', 'hover'],
34
            toggle: '- :first',
35
            boundary: window,
36
            boundaryAlign: false,
37
            delayShow: 0,
38
            delayHide: 800,
39
            clsDrop: false,
40
            hoverIdle: 200,
41
            animation: ['uk-animation-fade'],
42
            cls: 'uk-open'
43
        },
44

    
45
        init() {
46
            this.tracker = new MouseTracker();
47
            this.clsDrop = this.clsDrop || `uk-${this.$options.name}`;
48
            this.clsPos = this.clsDrop;
49

    
50
            this.$el.addClass(this.clsDrop);
51
        },
52

    
53
        ready() {
54

    
55
            this.updateAria(this.$el);
56

    
57
            if (this.toggle) {
58
                this.toggle = UIkit.toggle(query(this.toggle, this.$el), {target: this.$el, mode: this.mode});
59
            }
60

    
61
        },
62

    
63
        events: [
64

    
65
            {
66

    
67
                name: 'click',
68

    
69
                delegate() {
70
                    return `.${this.clsDrop}-close`;
71
                },
72

    
73
                handler(e) {
74
                    e.preventDefault();
75
                    this.hide(false);
76
                }
77

    
78
            },
79

    
80
            {
81

    
82
                name: 'click',
83

    
84
                delegate() {
85
                    return 'a[href^="#"]';
86
                },
87

    
88
                handler(e) {
89

    
90
                    if (e.isDefaultPrevented()) {
91
                        return;
92
                    }
93

    
94
                    var id = $(e.target).attr('href');
95

    
96
                    if (id.length === 1) {
97
                        e.preventDefault();
98
                    }
99

    
100
                    if (id.length === 1 || !isWithin(id, this.$el)) {
101
                        this.hide(false);
102
                    }
103
                }
104

    
105
            },
106

    
107
            {
108

    
109
                name: 'toggle',
110

    
111
                handler(e, toggle) {
112

    
113
                    if (toggle && !this.$el.is(toggle.target)) {
114
                        return;
115
                    }
116

    
117
                    e.preventDefault();
118

    
119
                    if (this.isToggled()) {
120
                        this.hide(false);
121
                    } else {
122
                        this.show(toggle, false);
123
                    }
124
                }
125

    
126
            },
127

    
128
            {
129

    
130
                name: pointerEnter,
131

    
132
                filter() {
133
                    return ~this.mode.indexOf('hover');
134
                },
135

    
136
                handler(e) {
137

    
138
                    if (isTouch(e)) {
139
                        return;
140
                    }
141

    
142
                    if (active
143
                        && active !== this
144
                        && active.toggle
145
                        && ~active.toggle.mode.indexOf('hover')
146
                        && !isWithin(e.target, active.$el)
147
                        && !isWithin(e.target, active.toggle.$el)
148
                    ) {
149
                        active.hide(false);
150
                    }
151

    
152
                    e.preventDefault();
153
                    this.show(this.toggle);
154
                }
155

    
156
            },
157

    
158
            {
159

    
160
                name: 'toggleshow',
161

    
162
                handler(e, toggle) {
163

    
164
                    if (toggle && !this.$el.is(toggle.target)) {
165
                        return;
166
                    }
167

    
168
                    e.preventDefault();
169
                    this.show(toggle || this.toggle);
170
                }
171

    
172
            },
173

    
174
            {
175

    
176
                name: `togglehide ${pointerLeave}`,
177

    
178
                handler(e, toggle) {
179

    
180
                    if (isTouch(e) || toggle && !this.$el.is(toggle.target)) {
181
                        return;
182
                    }
183

    
184
                    e.preventDefault();
185

    
186
                    if (this.toggle && ~this.toggle.mode.indexOf('hover')) {
187
                        this.hide();
188
                    }
189
                }
190

    
191
            },
192

    
193
            {
194

    
195
                name: 'beforeshow',
196

    
197
                self: true,
198

    
199
                handler() {
200
                    this.clearTimers();
201
                }
202

    
203
            },
204

    
205
            {
206

    
207
                name: 'show',
208

    
209
                self: true,
210

    
211
                handler() {
212
                    this.tracker.init();
213
                    this.toggle.$el.addClass(this.cls).attr('aria-expanded', 'true');
214
                }
215

    
216
            },
217

    
218
            {
219

    
220
                name: 'beforehide',
221

    
222
                self: true,
223

    
224
                handler() {
225
                    this.clearTimers();
226
                }
227

    
228
            },
229

    
230
            {
231

    
232
                name: 'hide',
233

    
234
                handler({target}) {
235

    
236
                    if (!this.$el.is(target)) {
237
                        active = active === null && isWithin(target, this.$el) && this.isToggled() ? this : active;
238
                        return;
239
                    }
240

    
241
                    active = this.isActive() ? null : active;
242
                    this.toggle.$el.removeClass(this.cls).attr('aria-expanded', 'false').blur().find('a, button').blur();
243
                    this.tracker.cancel();
244
                }
245

    
246
            }
247

    
248
        ],
249

    
250
        update: {
251

    
252
            write() {
253

    
254
                if (this.isToggled() && !Animation.inProgress(this.$el)) {
255
                    this.position();
256
                }
257

    
258
            },
259

    
260
            events: ['resize']
261

    
262
        },
263

    
264
        methods: {
265

    
266
            show(toggle, delay = true) {
267

    
268
                var show = () => {
269
                        if (!this.isToggled()) {
270
                            this.position();
271
                            this.toggleElement(this.$el, true);
272
                        }
273
                    },
274
                    tryShow = () => {
275

    
276
                        this.toggle = toggle || this.toggle;
277

    
278
                        this.clearTimers();
279

    
280
                        if (this.isActive()) {
281
                            return;
282
                        } else if (delay && active && active !== this && active.isDelaying) {
283
                            this.showTimer = setTimeout(this.show, 10);
284
                            return;
285
                        } else if (this.isParentOf(active)) {
286

    
287
                            if (active.hideTimer) {
288
                                active.hide(false);
289
                            } else {
290
                                return;
291
                            }
292

    
293
                        } else if (active && !this.isChildOf(active) && !this.isParentOf(active)) {
294
                            var prev;
295
                            while (active && active !== prev) {
296
                                prev = active;
297
                                active.hide(false);
298
                            }
299
                        }
300

    
301
                        if (delay && this.delayShow) {
302
                            this.showTimer = setTimeout(show, this.delayShow);
303
                        } else {
304
                            show();
305
                        }
306

    
307
                        active = this;
308
                    };
309

    
310
                if (toggle && this.toggle && !this.toggle.$el.is(toggle.$el)) {
311

    
312
                    this.$el.one('hide', tryShow);
313
                    this.hide(false);
314

    
315
                } else {
316
                    tryShow();
317
                }
318
            },
319

    
320
            hide(delay = true) {
321

    
322
                var hide = () => this.toggleNow(this.$el, false);
323

    
324
                this.clearTimers();
325

    
326
                this.isDelaying = this.tracker.movesTo(this.$el);
327

    
328
                if (delay && this.isDelaying) {
329
                    this.hideTimer = setTimeout(this.hide, this.hoverIdle);
330
                } else if (delay && this.delayHide) {
331
                    this.hideTimer = setTimeout(hide, this.delayHide);
332
                } else {
333
                    hide();
334
                }
335
            },
336

    
337
            clearTimers() {
338
                clearTimeout(this.showTimer);
339
                clearTimeout(this.hideTimer);
340
                this.showTimer = null;
341
                this.hideTimer = null;
342
                this.isDelaying = false;
343
            },
344

    
345
            isActive() {
346
                return active === this;
347
            },
348

    
349
            isChildOf(drop) {
350
                return drop && drop !== this && isWithin(this.$el, drop.$el);
351
            },
352

    
353
            isParentOf(drop) {
354
                return drop && drop !== this && isWithin(drop.$el, this.$el);
355
            },
356

    
357
            position() {
358

    
359
                removeClass(this.$el, `${this.clsDrop}-(stack|boundary)`).css({top: '', left: ''});
360

    
361
                this.$el.show().toggleClass(`${this.clsDrop}-boundary`, this.boundaryAlign);
362

    
363
                var boundary = getDimensions(this.boundary), alignTo = this.boundaryAlign ? boundary : getDimensions(this.toggle.$el);
364

    
365
                if (this.align === 'justify') {
366
                    var prop = this.getAxis() === 'y' ? 'width' : 'height';
367
                    this.$el.css(prop, alignTo[prop]);
368
                } else if (this.$el.outerWidth() > Math.max(boundary.right - alignTo.left, alignTo.right - boundary.left)) {
369
                    this.$el.addClass(`${this.clsDrop}-stack`);
370
                    this.$el.trigger('stack', [this]);
371
                }
372

    
373
                this.positionAt(this.$el, this.boundaryAlign ? this.boundary : this.toggle.$el, this.boundary);
374

    
375
                this.$el[0].style.display = '';
376

    
377
            }
378

    
379
        }
380

    
381
    });
382

    
383
    UIkit.drop.getActive = () => active;
384
}
(4-4/28)