~jonas-drange/ubuntu-webcatalog/tos-redirect

« back to all changes in this revision

Viewing changes to src/webcatalog/static/yui/3.10.3/build/autocomplete-list/autocomplete-list.js

  • Committer: Tarmac
  • Author(s): Stephen Stewart
  • Date: 2013-06-26 09:19:32 UTC
  • mfrom: (184.1.4 ubuntu-global-nav)
  • Revision ID: tarmac-20130626091932-8urtuli368k8p7ds
[r=beuno,jonas-drange] add ubuntu global nav to apps.ubuntu.com

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
YUI 3.10.3 (build 2fb5187)
 
3
Copyright 2013 Yahoo! Inc. All rights reserved.
 
4
Licensed under the BSD License.
 
5
http://yuilibrary.com/license/
 
6
*/
 
7
 
 
8
YUI.add('autocomplete-list', function (Y, NAME) {
 
9
 
 
10
/**
 
11
Traditional autocomplete dropdown list widget, just like Mom used to make.
 
12
 
 
13
@module autocomplete
 
14
@submodule autocomplete-list
 
15
**/
 
16
 
 
17
/**
 
18
Traditional autocomplete dropdown list widget, just like Mom used to make.
 
19
 
 
20
@class AutoCompleteList
 
21
@extends Widget
 
22
@uses AutoCompleteBase
 
23
@uses WidgetPosition
 
24
@uses WidgetPositionAlign
 
25
@constructor
 
26
@param {Object} config Configuration object.
 
27
**/
 
28
 
 
29
var Lang   = Y.Lang,
 
30
    Node   = Y.Node,
 
31
    YArray = Y.Array,
 
32
 
 
33
    // Whether or not we need an iframe shim.
 
34
    useShim = Y.UA.ie && Y.UA.ie < 7,
 
35
 
 
36
    // keyCode constants.
 
37
    KEY_TAB = 9,
 
38
 
 
39
    // String shorthand.
 
40
    _CLASS_ITEM        = '_CLASS_ITEM',
 
41
    _CLASS_ITEM_ACTIVE = '_CLASS_ITEM_ACTIVE',
 
42
    _CLASS_ITEM_HOVER  = '_CLASS_ITEM_HOVER',
 
43
    _SELECTOR_ITEM     = '_SELECTOR_ITEM',
 
44
 
 
45
    ACTIVE_ITEM      = 'activeItem',
 
46
    ALWAYS_SHOW_LIST = 'alwaysShowList',
 
47
    CIRCULAR         = 'circular',
 
48
    HOVERED_ITEM     = 'hoveredItem',
 
49
    ID               = 'id',
 
50
    ITEM             = 'item',
 
51
    LIST             = 'list',
 
52
    RESULT           = 'result',
 
53
    RESULTS          = 'results',
 
54
    VISIBLE          = 'visible',
 
55
    WIDTH            = 'width',
 
56
 
 
57
    // Event names.
 
58
    EVT_SELECT = 'select',
 
59
 
 
60
List = Y.Base.create('autocompleteList', Y.Widget, [
 
61
    Y.AutoCompleteBase,
 
62
    Y.WidgetPosition,
 
63
    Y.WidgetPositionAlign
 
64
], {
 
65
    // -- Prototype Properties -------------------------------------------------
 
66
    ARIA_TEMPLATE: '<div/>',
 
67
    ITEM_TEMPLATE: '<li/>',
 
68
    LIST_TEMPLATE: '<ul/>',
 
69
 
 
70
    // Widget automatically attaches delegated event handlers to everything in
 
71
    // Y.Node.DOM_EVENTS, including synthetic events. Since Widget's event
 
72
    // delegation won't work for the synthetic valuechange event, and since
 
73
    // it creates a name collision between the backcompat "valueChange" synth
 
74
    // event alias and AutoCompleteList's "valueChange" event for the "value"
 
75
    // attr, this hack is necessary in order to prevent Widget from attaching
 
76
    // valuechange handlers.
 
77
    UI_EVENTS: (function () {
 
78
        var uiEvents = Y.merge(Y.Node.DOM_EVENTS);
 
79
 
 
80
        delete uiEvents.valuechange;
 
81
        delete uiEvents.valueChange;
 
82
 
 
83
        return uiEvents;
 
84
    }()),
 
85
 
 
86
    // -- Lifecycle Prototype Methods ------------------------------------------
 
87
    initializer: function () {
 
88
        var inputNode = this.get('inputNode');
 
89
 
 
90
        if (!inputNode) {
 
91
            Y.error('No inputNode specified.');
 
92
            return;
 
93
        }
 
94
 
 
95
        this._inputNode  = inputNode;
 
96
        this._listEvents = [];
 
97
 
 
98
        // This ensures that the list is rendered inside the same parent as the
 
99
        // input node by default, which is necessary for proper ARIA support.
 
100
        this.DEF_PARENT_NODE = inputNode.get('parentNode');
 
101
 
 
102
        // Cache commonly used classnames and selectors for performance.
 
103
        this[_CLASS_ITEM]        = this.getClassName(ITEM);
 
104
        this[_CLASS_ITEM_ACTIVE] = this.getClassName(ITEM, 'active');
 
105
        this[_CLASS_ITEM_HOVER]  = this.getClassName(ITEM, 'hover');
 
106
        this[_SELECTOR_ITEM]     = '.' + this[_CLASS_ITEM];
 
107
 
 
108
        /**
 
109
        Fires when an autocomplete suggestion is selected from the list,
 
110
        typically via a keyboard action or mouse click.
 
111
 
 
112
        @event select
 
113
        @param {Node} itemNode List item node that was selected.
 
114
        @param {Object} result AutoComplete result object.
 
115
        @preventable _defSelectFn
 
116
        **/
 
117
        this.publish(EVT_SELECT, {
 
118
            defaultFn: this._defSelectFn
 
119
        });
 
120
    },
 
121
 
 
122
    destructor: function () {
 
123
        while (this._listEvents.length) {
 
124
            this._listEvents.pop().detach();
 
125
        }
 
126
 
 
127
        if (this._ariaNode) {
 
128
            this._ariaNode.remove().destroy(true);
 
129
        }
 
130
    },
 
131
 
 
132
    bindUI: function () {
 
133
        this._bindInput();
 
134
        this._bindList();
 
135
    },
 
136
 
 
137
    renderUI: function () {
 
138
        var ariaNode    = this._createAriaNode(),
 
139
            boundingBox = this.get('boundingBox'),
 
140
            contentBox  = this.get('contentBox'),
 
141
            inputNode   = this._inputNode,
 
142
            listNode    = this._createListNode(),
 
143
            parentNode  = inputNode.get('parentNode');
 
144
 
 
145
        inputNode.addClass(this.getClassName('input')).setAttrs({
 
146
            'aria-autocomplete': LIST,
 
147
            'aria-expanded'    : false,
 
148
            'aria-owns'        : listNode.get('id')
 
149
        });
 
150
 
 
151
        // ARIA node must be outside the widget or announcements won't be made
 
152
        // when the widget is hidden.
 
153
        parentNode.append(ariaNode);
 
154
 
 
155
        // Add an iframe shim for IE6.
 
156
        if (useShim) {
 
157
            boundingBox.plug(Y.Plugin.Shim);
 
158
        }
 
159
 
 
160
        this._ariaNode    = ariaNode;
 
161
        this._boundingBox = boundingBox;
 
162
        this._contentBox  = contentBox;
 
163
        this._listNode    = listNode;
 
164
        this._parentNode  = parentNode;
 
165
    },
 
166
 
 
167
    syncUI: function () {
 
168
        // No need to call _syncPosition() here; the other _sync methods will
 
169
        // call it when necessary.
 
170
        this._syncResults();
 
171
        this._syncVisibility();
 
172
    },
 
173
 
 
174
    // -- Public Prototype Methods ---------------------------------------------
 
175
 
 
176
    /**
 
177
    Hides the list, unless the `alwaysShowList` attribute is `true`.
 
178
 
 
179
    @method hide
 
180
    @see show
 
181
    @chainable
 
182
    **/
 
183
    hide: function () {
 
184
        return this.get(ALWAYS_SHOW_LIST) ? this : this.set(VISIBLE, false);
 
185
    },
 
186
 
 
187
    /**
 
188
    Selects the specified _itemNode_, or the current `activeItem` if _itemNode_
 
189
    is not specified.
 
190
 
 
191
    @method selectItem
 
192
    @param {Node} [itemNode] Item node to select.
 
193
    @param {EventFacade} [originEvent] Event that triggered the selection, if
 
194
        any.
 
195
    @chainable
 
196
    **/
 
197
    selectItem: function (itemNode, originEvent) {
 
198
        if (itemNode) {
 
199
            if (!itemNode.hasClass(this[_CLASS_ITEM])) {
 
200
                return this;
 
201
            }
 
202
        } else {
 
203
            itemNode = this.get(ACTIVE_ITEM);
 
204
 
 
205
            if (!itemNode) {
 
206
                return this;
 
207
            }
 
208
        }
 
209
 
 
210
        this.fire(EVT_SELECT, {
 
211
            itemNode   : itemNode,
 
212
            originEvent: originEvent || null,
 
213
            result     : itemNode.getData(RESULT)
 
214
        });
 
215
 
 
216
        return this;
 
217
    },
 
218
 
 
219
    // -- Protected Prototype Methods ------------------------------------------
 
220
 
 
221
    /**
 
222
    Activates the next item after the currently active item. If there is no next
 
223
    item and the `circular` attribute is `true`, focus will wrap back to the
 
224
    input node.
 
225
 
 
226
    @method _activateNextItem
 
227
    @chainable
 
228
    @protected
 
229
    **/
 
230
    _activateNextItem: function () {
 
231
        var item = this.get(ACTIVE_ITEM),
 
232
            nextItem;
 
233
 
 
234
        if (item) {
 
235
            nextItem = item.next(this[_SELECTOR_ITEM]) ||
 
236
                    (this.get(CIRCULAR) ? null : item);
 
237
        } else {
 
238
            nextItem = this._getFirstItemNode();
 
239
        }
 
240
 
 
241
        this.set(ACTIVE_ITEM, nextItem);
 
242
 
 
243
        return this;
 
244
    },
 
245
 
 
246
    /**
 
247
    Activates the item previous to the currently active item. If there is no
 
248
    previous item and the `circular` attribute is `true`, focus will wrap back
 
249
    to the input node.
 
250
 
 
251
    @method _activatePrevItem
 
252
    @chainable
 
253
    @protected
 
254
    **/
 
255
    _activatePrevItem: function () {
 
256
        var item     = this.get(ACTIVE_ITEM),
 
257
            prevItem = item ? item.previous(this[_SELECTOR_ITEM]) :
 
258
                    this.get(CIRCULAR) && this._getLastItemNode();
 
259
 
 
260
        this.set(ACTIVE_ITEM, prevItem || null);
 
261
 
 
262
        return this;
 
263
    },
 
264
 
 
265
    /**
 
266
    Appends the specified result _items_ to the list inside a new item node.
 
267
 
 
268
    @method _add
 
269
    @param {Array|Node|HTMLElement|String} items Result item or array of
 
270
        result items.
 
271
    @return {NodeList} Added nodes.
 
272
    @protected
 
273
    **/
 
274
    _add: function (items) {
 
275
        var itemNodes = [];
 
276
 
 
277
        YArray.each(Lang.isArray(items) ? items : [items], function (item) {
 
278
            itemNodes.push(this._createItemNode(item).setData(RESULT, item));
 
279
        }, this);
 
280
 
 
281
        itemNodes = Y.all(itemNodes);
 
282
        this._listNode.append(itemNodes.toFrag());
 
283
 
 
284
        return itemNodes;
 
285
    },
 
286
 
 
287
    /**
 
288
    Updates the ARIA live region with the specified message.
 
289
 
 
290
    @method _ariaSay
 
291
    @param {String} stringId String id (from the `strings` attribute) of the
 
292
        message to speak.
 
293
    @param {Object} [subs] Substitutions for placeholders in the string.
 
294
    @protected
 
295
    **/
 
296
    _ariaSay: function (stringId, subs) {
 
297
        var message = this.get('strings.' + stringId);
 
298
        this._ariaNode.set('text', subs ? Lang.sub(message, subs) : message);
 
299
    },
 
300
 
 
301
    /**
 
302
    Binds `inputNode` events and behavior.
 
303
 
 
304
    @method _bindInput
 
305
    @protected
 
306
    **/
 
307
    _bindInput: function () {
 
308
        var inputNode = this._inputNode,
 
309
            alignNode, alignWidth, tokenInput;
 
310
 
 
311
        // Null align means we can auto-align. Set align to false to prevent
 
312
        // auto-alignment, or a valid alignment config to customize the
 
313
        // alignment.
 
314
        if (this.get('align') === null) {
 
315
            // If this is a tokenInput, align with its bounding box.
 
316
            // Otherwise, align with the inputNode. Bit of a cheat.
 
317
            tokenInput = this.get('tokenInput');
 
318
            alignNode  = (tokenInput && tokenInput.get('boundingBox')) || inputNode;
 
319
 
 
320
            this.set('align', {
 
321
                node  : alignNode,
 
322
                points: ['tl', 'bl']
 
323
            });
 
324
 
 
325
            // If no width config is set, attempt to set the list's width to the
 
326
            // width of the alignment node. If the alignment node's width is
 
327
            // falsy, do nothing.
 
328
            if (!this.get(WIDTH) && (alignWidth = alignNode.get('offsetWidth'))) {
 
329
                this.set(WIDTH, alignWidth);
 
330
            }
 
331
        }
 
332
 
 
333
        // Attach inputNode events.
 
334
        this._listEvents = this._listEvents.concat([
 
335
            inputNode.after('blur',  this._afterListInputBlur, this),
 
336
            inputNode.after('focus', this._afterListInputFocus, this)
 
337
        ]);
 
338
    },
 
339
 
 
340
    /**
 
341
    Binds list events.
 
342
 
 
343
    @method _bindList
 
344
    @protected
 
345
    **/
 
346
    _bindList: function () {
 
347
        this._listEvents = this._listEvents.concat([
 
348
            Y.one('doc').after('click', this._afterDocClick, this),
 
349
            Y.one('win').after('windowresize', this._syncPosition, this),
 
350
 
 
351
            this.after({
 
352
                mouseover: this._afterMouseOver,
 
353
                mouseout : this._afterMouseOut,
 
354
 
 
355
                activeItemChange    : this._afterActiveItemChange,
 
356
                alwaysShowListChange: this._afterAlwaysShowListChange,
 
357
                hoveredItemChange   : this._afterHoveredItemChange,
 
358
                resultsChange       : this._afterResultsChange,
 
359
                visibleChange       : this._afterVisibleChange
 
360
            }),
 
361
 
 
362
            this._listNode.delegate('click', this._onItemClick,
 
363
                    this[_SELECTOR_ITEM], this)
 
364
        ]);
 
365
    },
 
366
 
 
367
    /**
 
368
    Clears the contents of the tray.
 
369
 
 
370
    @method _clear
 
371
    @protected
 
372
    **/
 
373
    _clear: function () {
 
374
        this.set(ACTIVE_ITEM, null);
 
375
        this._set(HOVERED_ITEM, null);
 
376
 
 
377
        this._listNode.get('children').remove(true);
 
378
    },
 
379
 
 
380
    /**
 
381
    Creates and returns an ARIA live region node.
 
382
 
 
383
    @method _createAriaNode
 
384
    @return {Node} ARIA node.
 
385
    @protected
 
386
    **/
 
387
    _createAriaNode: function () {
 
388
        var ariaNode = Node.create(this.ARIA_TEMPLATE);
 
389
 
 
390
        return ariaNode.addClass(this.getClassName('aria')).setAttrs({
 
391
            'aria-live': 'polite',
 
392
            role       : 'status'
 
393
        });
 
394
    },
 
395
 
 
396
    /**
 
397
    Creates and returns an item node with the specified _content_.
 
398
 
 
399
    @method _createItemNode
 
400
    @param {Object} result Result object.
 
401
    @return {Node} Item node.
 
402
    @protected
 
403
    **/
 
404
    _createItemNode: function (result) {
 
405
        var itemNode = Node.create(this.ITEM_TEMPLATE);
 
406
 
 
407
        return itemNode.addClass(this[_CLASS_ITEM]).setAttrs({
 
408
            id  : Y.stamp(itemNode),
 
409
            role: 'option'
 
410
        }).setAttribute('data-text', result.text).append(result.display);
 
411
    },
 
412
 
 
413
    /**
 
414
    Creates and returns a list node. If the `listNode` attribute is already set
 
415
    to an existing node, that node will be used.
 
416
 
 
417
    @method _createListNode
 
418
    @return {Node} List node.
 
419
    @protected
 
420
    **/
 
421
    _createListNode: function () {
 
422
        var listNode = this.get('listNode') || Node.create(this.LIST_TEMPLATE);
 
423
 
 
424
        listNode.addClass(this.getClassName(LIST)).setAttrs({
 
425
            id  : Y.stamp(listNode),
 
426
            role: 'listbox'
 
427
        });
 
428
 
 
429
        this._set('listNode', listNode);
 
430
        this.get('contentBox').append(listNode);
 
431
 
 
432
        return listNode;
 
433
    },
 
434
 
 
435
    /**
 
436
    Gets the first item node in the list, or `null` if the list is empty.
 
437
 
 
438
    @method _getFirstItemNode
 
439
    @return {Node|null}
 
440
    @protected
 
441
    **/
 
442
    _getFirstItemNode: function () {
 
443
        return this._listNode.one(this[_SELECTOR_ITEM]);
 
444
    },
 
445
 
 
446
    /**
 
447
    Gets the last item node in the list, or `null` if the list is empty.
 
448
 
 
449
    @method _getLastItemNode
 
450
    @return {Node|null}
 
451
    @protected
 
452
    **/
 
453
    _getLastItemNode: function () {
 
454
        return this._listNode.one(this[_SELECTOR_ITEM] + ':last-child');
 
455
    },
 
456
 
 
457
    /**
 
458
    Synchronizes the result list's position and alignment.
 
459
 
 
460
    @method _syncPosition
 
461
    @protected
 
462
    **/
 
463
    _syncPosition: function () {
 
464
        // Force WidgetPositionAlign to refresh its alignment.
 
465
        this._syncUIPosAlign();
 
466
 
 
467
        // Resize the IE6 iframe shim to match the list's dimensions.
 
468
        this._syncShim();
 
469
    },
 
470
 
 
471
    /**
 
472
    Synchronizes the results displayed in the list with those in the _results_
 
473
    argument, or with the `results` attribute if an argument is not provided.
 
474
 
 
475
    @method _syncResults
 
476
    @param {Array} [results] Results.
 
477
    @protected
 
478
    **/
 
479
    _syncResults: function (results) {
 
480
        if (!results) {
 
481
            results = this.get(RESULTS);
 
482
        }
 
483
 
 
484
        this._clear();
 
485
 
 
486
        if (results.length) {
 
487
            this._add(results);
 
488
            this._ariaSay('items_available');
 
489
        }
 
490
 
 
491
        this._syncPosition();
 
492
 
 
493
        if (this.get('activateFirstItem') && !this.get(ACTIVE_ITEM)) {
 
494
            this.set(ACTIVE_ITEM, this._getFirstItemNode());
 
495
        }
 
496
    },
 
497
 
 
498
    /**
 
499
    Synchronizes the size of the iframe shim used for IE6 and lower. In other
 
500
    browsers, this method is a noop.
 
501
 
 
502
    @method _syncShim
 
503
    @protected
 
504
    **/
 
505
    _syncShim: useShim ? function () {
 
506
        var shim = this._boundingBox.shim;
 
507
 
 
508
        if (shim) {
 
509
            shim.sync();
 
510
        }
 
511
    } : function () {},
 
512
 
 
513
    /**
 
514
    Synchronizes the visibility of the tray with the _visible_ argument, or with
 
515
    the `visible` attribute if an argument is not provided.
 
516
 
 
517
    @method _syncVisibility
 
518
    @param {Boolean} [visible] Visibility.
 
519
    @protected
 
520
    **/
 
521
    _syncVisibility: function (visible) {
 
522
        if (this.get(ALWAYS_SHOW_LIST)) {
 
523
            visible = true;
 
524
            this.set(VISIBLE, visible);
 
525
        }
 
526
 
 
527
        if (typeof visible === 'undefined') {
 
528
            visible = this.get(VISIBLE);
 
529
        }
 
530
 
 
531
        this._inputNode.set('aria-expanded', visible);
 
532
        this._boundingBox.set('aria-hidden', !visible);
 
533
 
 
534
        if (visible) {
 
535
            this._syncPosition();
 
536
        } else {
 
537
            this.set(ACTIVE_ITEM, null);
 
538
            this._set(HOVERED_ITEM, null);
 
539
 
 
540
            // Force a reflow to work around a glitch in IE6 and 7 where some of
 
541
            // the contents of the list will sometimes remain visible after the
 
542
            // container is hidden.
 
543
            this._boundingBox.get('offsetWidth');
 
544
        }
 
545
 
 
546
        // In some pages, IE7 fails to repaint the contents of the list after it
 
547
        // becomes visible. Toggling a bogus class on the body forces a repaint
 
548
        // that fixes the issue.
 
549
        if (Y.UA.ie === 7) {
 
550
            // Note: We don't actually need to use ClassNameManager here. This
 
551
            // class isn't applying any actual styles; it's just frobbing the
 
552
            // body element to force a repaint. The actual class name doesn't
 
553
            // really matter.
 
554
            Y.one('body')
 
555
                .addClass('yui3-ie7-sucks')
 
556
                .removeClass('yui3-ie7-sucks');
 
557
        }
 
558
    },
 
559
 
 
560
    // -- Protected Event Handlers ---------------------------------------------
 
561
 
 
562
    /**
 
563
    Handles `activeItemChange` events.
 
564
 
 
565
    @method _afterActiveItemChange
 
566
    @param {EventFacade} e
 
567
    @protected
 
568
    **/
 
569
    _afterActiveItemChange: function (e) {
 
570
        var inputNode = this._inputNode,
 
571
            newVal    = e.newVal,
 
572
            prevVal   = e.prevVal,
 
573
            node;
 
574
 
 
575
        // The previous item may have disappeared by the time this handler runs,
 
576
        // so we need to be careful.
 
577
        if (prevVal && prevVal._node) {
 
578
            prevVal.removeClass(this[_CLASS_ITEM_ACTIVE]);
 
579
        }
 
580
 
 
581
        if (newVal) {
 
582
            newVal.addClass(this[_CLASS_ITEM_ACTIVE]);
 
583
            inputNode.set('aria-activedescendant', newVal.get(ID));
 
584
        } else {
 
585
            inputNode.removeAttribute('aria-activedescendant');
 
586
        }
 
587
 
 
588
        if (this.get('scrollIntoView')) {
 
589
            node = newVal || inputNode;
 
590
 
 
591
            if (!node.inRegion(Y.DOM.viewportRegion(), true)
 
592
                    || !node.inRegion(this._contentBox, true)) {
 
593
 
 
594
                node.scrollIntoView();
 
595
            }
 
596
        }
 
597
    },
 
598
 
 
599
    /**
 
600
    Handles `alwaysShowListChange` events.
 
601
 
 
602
    @method _afterAlwaysShowListChange
 
603
    @param {EventFacade} e
 
604
    @protected
 
605
    **/
 
606
    _afterAlwaysShowListChange: function (e) {
 
607
        this.set(VISIBLE, e.newVal || this.get(RESULTS).length > 0);
 
608
    },
 
609
 
 
610
    /**
 
611
    Handles click events on the document. If the click is outside both the
 
612
    input node and the bounding box, the list will be hidden.
 
613
 
 
614
    @method _afterDocClick
 
615
    @param {EventFacade} e
 
616
    @protected
 
617
    @since 3.5.0
 
618
    **/
 
619
    _afterDocClick: function (e) {
 
620
        var boundingBox = this._boundingBox,
 
621
            target      = e.target;
 
622
 
 
623
        if(target !== this._inputNode && target !== boundingBox &&
 
624
                target.ancestor('#' + boundingBox.get('id'), true)){
 
625
            this.hide();
 
626
        }
 
627
    },
 
628
 
 
629
    /**
 
630
    Handles `hoveredItemChange` events.
 
631
 
 
632
    @method _afterHoveredItemChange
 
633
    @param {EventFacade} e
 
634
    @protected
 
635
    **/
 
636
    _afterHoveredItemChange: function (e) {
 
637
        var newVal  = e.newVal,
 
638
            prevVal = e.prevVal;
 
639
 
 
640
        if (prevVal) {
 
641
            prevVal.removeClass(this[_CLASS_ITEM_HOVER]);
 
642
        }
 
643
 
 
644
        if (newVal) {
 
645
            newVal.addClass(this[_CLASS_ITEM_HOVER]);
 
646
        }
 
647
    },
 
648
 
 
649
    /**
 
650
    Handles `inputNode` blur events.
 
651
 
 
652
    @method _afterListInputBlur
 
653
    @protected
 
654
    **/
 
655
    _afterListInputBlur: function () {
 
656
        this._listInputFocused = false;
 
657
 
 
658
        if (this.get(VISIBLE) &&
 
659
                !this._mouseOverList &&
 
660
                (this._lastInputKey !== KEY_TAB ||
 
661
                    !this.get('tabSelect') ||
 
662
                    !this.get(ACTIVE_ITEM))) {
 
663
            this.hide();
 
664
        }
 
665
    },
 
666
 
 
667
    /**
 
668
    Handles `inputNode` focus events.
 
669
 
 
670
    @method _afterListInputFocus
 
671
    @protected
 
672
    **/
 
673
    _afterListInputFocus: function () {
 
674
        this._listInputFocused = true;
 
675
    },
 
676
 
 
677
    /**
 
678
    Handles `mouseover` events.
 
679
 
 
680
    @method _afterMouseOver
 
681
    @param {EventFacade} e
 
682
    @protected
 
683
    **/
 
684
    _afterMouseOver: function (e) {
 
685
        var itemNode = e.domEvent.target.ancestor(this[_SELECTOR_ITEM], true);
 
686
 
 
687
        this._mouseOverList = true;
 
688
 
 
689
        if (itemNode) {
 
690
            this._set(HOVERED_ITEM, itemNode);
 
691
        }
 
692
    },
 
693
 
 
694
    /**
 
695
    Handles `mouseout` events.
 
696
 
 
697
    @method _afterMouseOut
 
698
    @param {EventFacade} e
 
699
    @protected
 
700
    **/
 
701
    _afterMouseOut: function () {
 
702
        this._mouseOverList = false;
 
703
        this._set(HOVERED_ITEM, null);
 
704
    },
 
705
 
 
706
    /**
 
707
    Handles `resultsChange` events.
 
708
 
 
709
    @method _afterResultsChange
 
710
    @param {EventFacade} e
 
711
    @protected
 
712
    **/
 
713
    _afterResultsChange: function (e) {
 
714
        this._syncResults(e.newVal);
 
715
 
 
716
        if (!this.get(ALWAYS_SHOW_LIST)) {
 
717
            this.set(VISIBLE, !!e.newVal.length);
 
718
        }
 
719
    },
 
720
 
 
721
    /**
 
722
    Handles `visibleChange` events.
 
723
 
 
724
    @method _afterVisibleChange
 
725
    @param {EventFacade} e
 
726
    @protected
 
727
    **/
 
728
    _afterVisibleChange: function (e) {
 
729
        this._syncVisibility(!!e.newVal);
 
730
    },
 
731
 
 
732
    /**
 
733
    Delegated event handler for item `click` events.
 
734
 
 
735
    @method _onItemClick
 
736
    @param {EventFacade} e
 
737
    @protected
 
738
    **/
 
739
    _onItemClick: function (e) {
 
740
        var itemNode = e.currentTarget;
 
741
 
 
742
        this.set(ACTIVE_ITEM, itemNode);
 
743
        this.selectItem(itemNode, e);
 
744
    },
 
745
 
 
746
    // -- Protected Default Event Handlers -------------------------------------
 
747
 
 
748
    /**
 
749
    Default `select` event handler.
 
750
 
 
751
    @method _defSelectFn
 
752
    @param {EventFacade} e
 
753
    @protected
 
754
    **/
 
755
    _defSelectFn: function (e) {
 
756
        var text = e.result.text;
 
757
 
 
758
        // TODO: support typeahead completion, etc.
 
759
        this._inputNode.focus();
 
760
        this._updateValue(text);
 
761
        this._ariaSay('item_selected', {item: text});
 
762
        this.hide();
 
763
    }
 
764
}, {
 
765
    ATTRS: {
 
766
        /**
 
767
        If `true`, the first item in the list will be activated by default when
 
768
        the list is initially displayed and when results change.
 
769
 
 
770
        @attribute activateFirstItem
 
771
        @type Boolean
 
772
        @default false
 
773
        **/
 
774
        activateFirstItem: {
 
775
            value: false
 
776
        },
 
777
 
 
778
        /**
 
779
        Item that's currently active, if any. When the user presses enter, this
 
780
        is the item that will be selected.
 
781
 
 
782
        @attribute activeItem
 
783
        @type Node
 
784
        **/
 
785
        activeItem: {
 
786
            setter: Y.one,
 
787
            value: null
 
788
        },
 
789
 
 
790
        /**
 
791
        If `true`, the list will remain visible even when there are no results
 
792
        to display.
 
793
 
 
794
        @attribute alwaysShowList
 
795
        @type Boolean
 
796
        @default false
 
797
        **/
 
798
        alwaysShowList: {
 
799
            value: false
 
800
        },
 
801
 
 
802
        /**
 
803
        If `true`, keyboard navigation will wrap around to the opposite end of
 
804
        the list when navigating past the first or last item.
 
805
 
 
806
        @attribute circular
 
807
        @type Boolean
 
808
        @default true
 
809
        **/
 
810
        circular: {
 
811
            value: true
 
812
        },
 
813
 
 
814
        /**
 
815
        Item currently being hovered over by the mouse, if any.
 
816
 
 
817
        @attribute hoveredItem
 
818
        @type Node|null
 
819
        @readOnly
 
820
        **/
 
821
        hoveredItem: {
 
822
            readOnly: true,
 
823
            value: null
 
824
        },
 
825
 
 
826
        /**
 
827
        Node that will contain result items.
 
828
 
 
829
        @attribute listNode
 
830
        @type Node|null
 
831
        @initOnly
 
832
        **/
 
833
        listNode: {
 
834
            writeOnce: 'initOnly',
 
835
            value: null
 
836
        },
 
837
 
 
838
        /**
 
839
        If `true`, the viewport will be scrolled to ensure that the active list
 
840
        item is visible when necessary.
 
841
 
 
842
        @attribute scrollIntoView
 
843
        @type Boolean
 
844
        @default false
 
845
        **/
 
846
        scrollIntoView: {
 
847
            value: false
 
848
        },
 
849
 
 
850
        /**
 
851
        Translatable strings used by the AutoCompleteList widget.
 
852
 
 
853
        @attribute strings
 
854
        @type Object
 
855
        **/
 
856
        strings: {
 
857
            valueFn: function () {
 
858
                return Y.Intl.get('autocomplete-list');
 
859
            }
 
860
        },
 
861
 
 
862
        /**
 
863
        If `true`, pressing the tab key while the list is visible will select
 
864
        the active item, if any.
 
865
 
 
866
        @attribute tabSelect
 
867
        @type Boolean
 
868
        @default true
 
869
        **/
 
870
        tabSelect: {
 
871
            value: true
 
872
        },
 
873
 
 
874
        // The "visible" attribute is documented in Widget.
 
875
        visible: {
 
876
            value: false
 
877
        }
 
878
    },
 
879
 
 
880
    CSS_PREFIX: Y.ClassNameManager.getClassName('aclist')
 
881
});
 
882
 
 
883
Y.AutoCompleteList = List;
 
884
 
 
885
/**
 
886
Alias for <a href="AutoCompleteList.html">`AutoCompleteList`</a>. See that class
 
887
for API docs.
 
888
 
 
889
@class AutoComplete
 
890
**/
 
891
 
 
892
Y.AutoComplete = List;
 
893
 
 
894
 
 
895
}, '3.10.3', {
 
896
    "lang": [
 
897
        "en",
 
898
        "es",
 
899
        "it"
 
900
    ],
 
901
    "requires": [
 
902
        "autocomplete-base",
 
903
        "event-resize",
 
904
        "node-screen",
 
905
        "selector-css3",
 
906
        "shim-plugin",
 
907
        "widget",
 
908
        "widget-position",
 
909
        "widget-position-align"
 
910
    ],
 
911
    "skinnable": true
 
912
});