Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/ui/palette.js
4116 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview A palette control. A palette is a grid that the user can
9
* highlight or select via the keyboard or the mouse.
10
*
11
* @see ../demos/palette.html
12
*/
13
14
goog.provide('goog.ui.Palette');
15
16
goog.require('goog.asserts');
17
goog.require('goog.dom');
18
goog.require('goog.events');
19
goog.require('goog.events.EventType');
20
goog.require('goog.events.KeyCodes');
21
goog.require('goog.math.Size');
22
goog.require('goog.style');
23
goog.require('goog.ui.Component');
24
goog.require('goog.ui.Control');
25
goog.require('goog.ui.PaletteRenderer');
26
goog.require('goog.ui.SelectionModel');
27
goog.requireType('goog.events.BrowserEvent');
28
goog.requireType('goog.events.Event');
29
goog.requireType('goog.events.KeyEvent');
30
goog.requireType('goog.ui.ControlContent');
31
32
33
34
/**
35
* A palette is a grid of DOM nodes that the user can highlight or select via
36
* the keyboard or the mouse. The selection state of the palette is controlled
37
* an ACTION event. Event listeners may retrieve the selected item using the
38
* {@link #getSelectedItem} or {@link #getSelectedIndex} method.
39
*
40
* Use this class as the base for components like color palettes or emoticon
41
* pickers. Use {@link #setContent} to set/change the items in the palette
42
* after construction. See palette.html demo for example usage.
43
*
44
* @param {Array<Node>} items Array of DOM nodes to be displayed as items
45
* in the palette grid (limited to one per cell).
46
* @param {goog.ui.PaletteRenderer=} opt_renderer Renderer used to render or
47
* decorate the palette; defaults to {@link goog.ui.PaletteRenderer}.
48
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
49
* document interaction.
50
* @constructor
51
* @extends {goog.ui.Control}
52
*/
53
goog.ui.Palette = function(items, opt_renderer, opt_domHelper) {
54
'use strict';
55
goog.ui.Palette.base(
56
this, 'constructor', items,
57
opt_renderer || goog.ui.PaletteRenderer.getInstance(), opt_domHelper);
58
this.setAutoStates(
59
goog.ui.Component.State.CHECKED | goog.ui.Component.State.SELECTED |
60
goog.ui.Component.State.OPENED,
61
false);
62
63
/**
64
* A fake component for dispatching events on palette cell changes.
65
* @type {!goog.ui.Palette.CurrentCell_}
66
* @private
67
*/
68
this.currentCellControl_ = new goog.ui.Palette.CurrentCell_();
69
this.currentCellControl_.setParentEventTarget(this);
70
71
/**
72
* @private {number} The last highlighted index, or -1 if it never had one.
73
*/
74
this.lastHighlightedIndex_ = -1;
75
};
76
goog.inherits(goog.ui.Palette, goog.ui.Control);
77
78
79
/**
80
* Events fired by the palette object
81
* @enum {string}
82
*/
83
goog.ui.Palette.EventType = {
84
AFTER_HIGHLIGHT: goog.events.getUniqueId('afterhighlight')
85
};
86
87
88
/**
89
* Palette dimensions (columns x rows). If the number of rows is undefined,
90
* it is calculated on first use.
91
* @type {?goog.math.Size}
92
* @private
93
*/
94
goog.ui.Palette.prototype.size_ = null;
95
96
97
/**
98
* Index of the currently highlighted item (-1 if none).
99
* @type {number}
100
* @private
101
*/
102
goog.ui.Palette.prototype.highlightedIndex_ = -1;
103
104
105
/**
106
* Selection model controlling the palette's selection state.
107
* @type {?goog.ui.SelectionModel}
108
* @private
109
*/
110
goog.ui.Palette.prototype.selectionModel_ = null;
111
112
113
// goog.ui.Component / goog.ui.Control implementation.
114
115
116
/** @override */
117
goog.ui.Palette.prototype.disposeInternal = function() {
118
'use strict';
119
goog.ui.Palette.superClass_.disposeInternal.call(this);
120
121
if (this.selectionModel_) {
122
this.selectionModel_.dispose();
123
this.selectionModel_ = null;
124
}
125
126
this.size_ = null;
127
128
this.currentCellControl_.dispose();
129
};
130
131
132
/**
133
* Overrides {@link goog.ui.Control#setContentInternal} by also updating the
134
* grid size and the selection model. Considered protected.
135
* @param {goog.ui.ControlContent} content Array of DOM nodes to be displayed
136
* as items in the palette grid (one item per cell).
137
* @protected
138
* @override
139
*/
140
goog.ui.Palette.prototype.setContentInternal = function(content) {
141
'use strict';
142
var items = /** @type {Array<Node>} */ (content);
143
goog.ui.Palette.superClass_.setContentInternal.call(this, items);
144
145
// Adjust the palette size.
146
this.adjustSize_();
147
148
// Add the items to the selection model, replacing previous items (if any).
149
if (this.selectionModel_) {
150
// We already have a selection model; just replace the items.
151
this.selectionModel_.clear();
152
this.selectionModel_.addItems(items);
153
} else {
154
// Create a selection model, initialize the items, and hook up handlers.
155
this.selectionModel_ = new goog.ui.SelectionModel(items);
156
this.selectionModel_.setSelectionHandler(goog.bind(this.selectItem_, this));
157
this.getHandler().listen(
158
this.selectionModel_, goog.events.EventType.SELECT,
159
this.handleSelectionChange);
160
}
161
162
// In all cases, clear the highlight.
163
this.highlightedIndex_ = -1;
164
};
165
166
167
/**
168
* Overrides {@link goog.ui.Control#getCaption} to return the empty string,
169
* since palettes don't have text captions.
170
* @return {string} The empty string.
171
* @override
172
*/
173
goog.ui.Palette.prototype.getCaption = function() {
174
'use strict';
175
return '';
176
};
177
178
179
/**
180
* Overrides {@link goog.ui.Control#setCaption} to be a no-op, since palettes
181
* don't have text captions.
182
* @param {string} caption Ignored.
183
* @override
184
*/
185
goog.ui.Palette.prototype.setCaption = function(caption) {
186
// Do nothing.
187
};
188
189
190
// Palette event handling.
191
192
193
/**
194
* Handles mouseover events. Overrides {@link goog.ui.Control#handleMouseOver}
195
* by determining which palette item (if any) was moused over, highlighting it,
196
* and un-highlighting any previously-highlighted item.
197
* @param {goog.events.BrowserEvent} e Mouse event to handle.
198
* @override
199
*/
200
goog.ui.Palette.prototype.handleMouseOver = function(e) {
201
'use strict';
202
goog.ui.Palette.superClass_.handleMouseOver.call(this, e);
203
204
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
205
var item = this.getRenderer().getContainingItem(this, e.target);
206
if (item && e.relatedTarget && goog.dom.contains(item, e.relatedTarget)) {
207
// Ignore internal mouse moves.
208
return;
209
}
210
211
if (item != this.getHighlightedItem()) {
212
this.setHighlightedItem(item);
213
}
214
};
215
216
217
/**
218
* Handles mousedown events. Overrides {@link goog.ui.Control#handleMouseDown}
219
* by ensuring that the item on which the user moused down is highlighted.
220
* @param {goog.events.Event} e Mouse event to handle.
221
* @override
222
*/
223
goog.ui.Palette.prototype.handleMouseDown = function(e) {
224
'use strict';
225
goog.ui.Palette.superClass_.handleMouseDown.call(this, e);
226
227
if (this.isActive()) {
228
// Make sure we move the highlight to the cell on which the user moused
229
// down.
230
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
231
var item = this.getRenderer().getContainingItem(this, e.target);
232
if (item != this.getHighlightedItem()) {
233
this.setHighlightedItem(item);
234
}
235
}
236
};
237
238
239
/**
240
* Selects the currently highlighted palette item (triggered by mouseup or by
241
* keyboard action). Overrides {@link goog.ui.Control#performActionInternal}
242
* by selecting the highlighted item and dispatching an ACTION event.
243
* @param {goog.events.Event} e Mouse or key event that triggered the action.
244
* @return {boolean} True if the action was allowed to proceed, false otherwise.
245
* @override
246
*/
247
goog.ui.Palette.prototype.performActionInternal = function(e) {
248
'use strict';
249
var highlightedItem = this.getHighlightedItem();
250
if (highlightedItem) {
251
if (e && this.shouldSelectHighlightedItem_(e)) {
252
this.setSelectedItem(highlightedItem);
253
}
254
return goog.ui.Palette.base(this, 'performActionInternal', e);
255
}
256
return false;
257
};
258
259
260
/**
261
* Determines whether to select the highlighted item while handling an internal
262
* action. The highlighted item should not be selected if the action is a mouse
263
* event occurring outside the palette or in an "empty" cell.
264
* @param {!goog.events.Event} e Mouseup or key event being handled.
265
* @return {boolean} True if the highlighted item should be selected.
266
* @private
267
* @suppress {strictMissingProperties} Added to tighten compiler checks
268
*/
269
goog.ui.Palette.prototype.shouldSelectHighlightedItem_ = function(e) {
270
'use strict';
271
if (!this.getSelectedItem()) {
272
// It's always ok to select when nothing is selected yet.
273
return true;
274
} else if (e.type != 'mouseup') {
275
// Keyboard can only act on valid cells.
276
return true;
277
} else {
278
// Return whether or not the mouse action was in the palette.
279
return !!this.getRenderer().getContainingItem(this, e.target);
280
}
281
};
282
283
284
/**
285
* Handles keyboard events dispatched while the palette has focus. Moves the
286
* highlight on arrow keys, and selects the highlighted item on Enter or Space.
287
* Returns true if the event was handled, false otherwise. In particular, if
288
* the user attempts to navigate out of the grid, the highlight isn't changed,
289
* and this method returns false; it is then up to the parent component to
290
* handle the event (e.g. by wrapping the highlight around). Overrides {@link
291
* goog.ui.Control#handleKeyEvent}.
292
* @param {goog.events.KeyEvent} e Key event to handle.
293
* @return {boolean} True iff the key event was handled by the component.
294
* @override
295
*/
296
goog.ui.Palette.prototype.handleKeyEvent = function(e) {
297
'use strict';
298
var items = this.getContent();
299
/** @suppress {strictMissingProperties} Added to tighten compiler checks */
300
var numItems = items ? items.length : 0;
301
var numColumns = this.size_.width;
302
303
// If the component is disabled or the palette is empty, bail.
304
if (numItems == 0 || !this.isEnabled()) {
305
return false;
306
}
307
308
// User hit ENTER or SPACE; trigger action.
309
if (e.keyCode == goog.events.KeyCodes.ENTER ||
310
e.keyCode == goog.events.KeyCodes.SPACE) {
311
return this.performActionInternal(e);
312
}
313
314
// User hit HOME or END; move highlight.
315
if (e.keyCode == goog.events.KeyCodes.HOME) {
316
this.setHighlightedIndexInternal_(0, true /* scrollIntoView */);
317
return true;
318
} else if (e.keyCode == goog.events.KeyCodes.END) {
319
this.setHighlightedIndexInternal_(numItems - 1, true /* scrollIntoView */);
320
return true;
321
}
322
323
// If nothing is highlighted, start from the selected index. If nothing is
324
// selected either, highlightedIndex is -1.
325
var highlightedIndex = this.highlightedIndex_ < 0 ? this.getSelectedIndex() :
326
this.highlightedIndex_;
327
328
switch (e.keyCode) {
329
case goog.events.KeyCodes.LEFT:
330
// If the highlighted index is uninitialized, or is at the beginning, move
331
// it to the end.
332
if (highlightedIndex == -1 || highlightedIndex == 0) {
333
highlightedIndex = numItems;
334
}
335
this.setHighlightedIndexInternal_(
336
highlightedIndex - 1, true /* scrollIntoView */);
337
e.preventDefault();
338
return true;
339
break;
340
341
case goog.events.KeyCodes.RIGHT:
342
// If the highlighted index at the end, move it to the beginning.
343
if (highlightedIndex == numItems - 1) {
344
highlightedIndex = -1;
345
}
346
this.setHighlightedIndexInternal_(
347
highlightedIndex + 1, true /* scrollIntoView */);
348
e.preventDefault();
349
return true;
350
break;
351
352
case goog.events.KeyCodes.UP:
353
if (highlightedIndex == -1) {
354
highlightedIndex = numItems + numColumns - 1;
355
}
356
if (highlightedIndex >= numColumns) {
357
this.setHighlightedIndexInternal_(
358
highlightedIndex - numColumns, true /* scrollIntoView */);
359
e.preventDefault();
360
return true;
361
}
362
break;
363
364
case goog.events.KeyCodes.DOWN:
365
if (highlightedIndex == -1) {
366
highlightedIndex = -numColumns;
367
}
368
if (highlightedIndex < numItems - numColumns) {
369
this.setHighlightedIndexInternal_(
370
highlightedIndex + numColumns, true /* scrollIntoView */);
371
e.preventDefault();
372
return true;
373
}
374
break;
375
}
376
377
return false;
378
};
379
380
381
/**
382
* Handles selection change events dispatched by the selection model.
383
* @param {goog.events.Event} e Selection event to handle.
384
*/
385
goog.ui.Palette.prototype.handleSelectionChange = function(e) {
386
// No-op in the base class.
387
};
388
389
390
// Palette management.
391
392
393
/**
394
* Returns the size of the palette grid.
395
* @return {goog.math.Size} Palette size (columns x rows).
396
*/
397
goog.ui.Palette.prototype.getSize = function() {
398
'use strict';
399
return this.size_;
400
};
401
402
403
/**
404
* Sets the size of the palette grid to the given size. Callers can either
405
* pass a single {@link goog.math.Size} or a pair of numbers (first the number
406
* of columns, then the number of rows) to this method. In both cases, the
407
* number of rows is optional and will be calculated automatically if needed.
408
* It is an error to attempt to change the size of the palette after it has
409
* been rendered.
410
* @param {goog.math.Size|number} size Either a size object or the number of
411
* columns.
412
* @param {number=} opt_rows The number of rows (optional).
413
*/
414
goog.ui.Palette.prototype.setSize = function(size, opt_rows) {
415
'use strict';
416
if (this.getElement()) {
417
throw new Error(goog.ui.Component.Error.ALREADY_RENDERED);
418
}
419
420
this.size_ = (typeof size === 'number') ?
421
new goog.math.Size(size, /** @type {number} */ (opt_rows)) :
422
size;
423
424
// Adjust size, if needed.
425
this.adjustSize_();
426
};
427
428
429
/**
430
* Returns the 0-based index of the currently highlighted palette item, or -1
431
* if no item is highlighted.
432
* @return {number} Index of the highlighted item (-1 if none).
433
*/
434
goog.ui.Palette.prototype.getHighlightedIndex = function() {
435
'use strict';
436
return this.highlightedIndex_;
437
};
438
439
440
/**
441
* Returns the currently highlighted palette item, or null if no item is
442
* highlighted.
443
* @return {Node} The highlighted item (undefined if none).
444
*/
445
goog.ui.Palette.prototype.getHighlightedItem = function() {
446
'use strict';
447
var items = this.getContent();
448
return items && items[this.highlightedIndex_];
449
};
450
451
452
/**
453
* @return {Element} The highlighted cell.
454
* @private
455
* @suppress {strictMissingProperties} Added to tighten compiler checks
456
*/
457
goog.ui.Palette.prototype.getHighlightedCellElement_ = function() {
458
'use strict';
459
return this.getRenderer().getCellForItem(this.getHighlightedItem());
460
};
461
462
463
/**
464
* Highlights the item at the given 0-based index, or removes the highlight
465
* if the argument is -1 or out of range. Any previously-highlighted item
466
* will be un-highlighted.
467
* @param {number} index 0-based index of the item to highlight.
468
*/
469
goog.ui.Palette.prototype.setHighlightedIndex = function(index) {
470
'use strict';
471
this.setHighlightedIndexInternal_(index, false /* scrollIntoView */);
472
};
473
474
475
/**
476
* @param {number} index 0-based index of the item to highlight.
477
* @param {boolean} scrollIntoView Whether to bring the highlighted item into
478
* view by potentially scrolling the palette's container. This has no effect
479
* if the palette is not in a scrollbale container.
480
* @private
481
*/
482
goog.ui.Palette.prototype.setHighlightedIndexInternal_ = function(
483
index, scrollIntoView) {
484
'use strict';
485
if (index != this.highlightedIndex_) {
486
this.highlightIndex_(this.highlightedIndex_, false);
487
this.lastHighlightedIndex_ = this.highlightedIndex_;
488
this.highlightedIndex_ = index;
489
this.highlightIndex_(index, true);
490
if (scrollIntoView && this.getParent()) {
491
var highlightedElement = goog.asserts.assert(
492
this.getHighlightedCellElement_(),
493
'Highlighted item must exist to scroll to make it visible in ' +
494
'container. Please check that index is non-negative and valid.');
495
goog.style.scrollIntoContainerView(
496
highlightedElement, this.getParent().getElementStrict());
497
}
498
this.dispatchEvent(goog.ui.Palette.EventType.AFTER_HIGHLIGHT);
499
}
500
};
501
502
503
/**
504
* Highlights the given item, or removes the highlight if the argument is null
505
* or invalid. Any previously-highlighted item will be un-highlighted.
506
* @param {Node|undefined} item Item to highlight.
507
*/
508
goog.ui.Palette.prototype.setHighlightedItem = function(item) {
509
'use strict';
510
var items = /** @type {Array<Node>} */ (this.getContent());
511
this.setHighlightedIndex((items && item) ? items.indexOf(item) : -1);
512
};
513
514
515
/**
516
* Returns the 0-based index of the currently selected palette item, or -1
517
* if no item is selected.
518
* @return {number} Index of the selected item (-1 if none).
519
*/
520
goog.ui.Palette.prototype.getSelectedIndex = function() {
521
'use strict';
522
return this.selectionModel_ ? this.selectionModel_.getSelectedIndex() : -1;
523
};
524
525
526
/**
527
* Returns the currently selected palette item, or null if no item is selected.
528
* @return {Node} The selected item (null if none).
529
*/
530
goog.ui.Palette.prototype.getSelectedItem = function() {
531
'use strict';
532
return this.selectionModel_ ?
533
/** @type {Node} */ (this.selectionModel_.getSelectedItem()) :
534
null;
535
};
536
537
538
/**
539
* Selects the item at the given 0-based index, or clears the selection
540
* if the argument is -1 or out of range. Any previously-selected item
541
* will be deselected.
542
* @param {number} index 0-based index of the item to select.
543
*/
544
goog.ui.Palette.prototype.setSelectedIndex = function(index) {
545
'use strict';
546
if (this.selectionModel_) {
547
this.selectionModel_.setSelectedIndex(index);
548
}
549
};
550
551
552
/**
553
* Selects the given item, or clears the selection if the argument is null or
554
* invalid. Any previously-selected item will be deselected.
555
* @param {Node} item Item to select.
556
*/
557
goog.ui.Palette.prototype.setSelectedItem = function(item) {
558
'use strict';
559
if (this.selectionModel_) {
560
this.selectionModel_.setSelectedItem(item);
561
}
562
};
563
564
565
/**
566
* Private helper; highlights or un-highlights the item at the given index
567
* based on the value of the Boolean argument. This implementation simply
568
* applies highlight styling to the cell containing the item to be highighted.
569
* Does nothing if the palette hasn't been rendered yet.
570
* @param {number} index 0-based index of item to highlight or un-highlight.
571
* @param {boolean} highlight If true, the item is highlighted; otherwise it
572
* is un-highlighted.
573
* @private
574
* @suppress {strictMissingProperties} Added to tighten compiler checks
575
*/
576
goog.ui.Palette.prototype.highlightIndex_ = function(index, highlight) {
577
'use strict';
578
if (this.getElement()) {
579
var items = this.getContent();
580
if (items && index >= 0 && index < items.length) {
581
var cellEl = this.getHighlightedCellElement_();
582
if (this.currentCellControl_.getElement() != cellEl) {
583
this.currentCellControl_.setElementInternal(cellEl);
584
}
585
if (this.currentCellControl_.tryHighlight(highlight)) {
586
this.getRenderer().highlightCell(this, items[index], highlight);
587
}
588
}
589
}
590
};
591
592
593
/** @override */
594
goog.ui.Palette.prototype.setHighlighted = function(highlight) {
595
'use strict';
596
if (highlight && this.highlightedIndex_ == -1) {
597
// If there was a last highlighted index, use that. Otherwise, highlight the
598
// first cell.
599
this.setHighlightedIndex(
600
this.lastHighlightedIndex_ > -1 ? this.lastHighlightedIndex_ : 0);
601
} else if (!highlight) {
602
this.setHighlightedIndex(-1);
603
}
604
// The highlight event should be fired once the component has updated its own
605
// state.
606
goog.ui.Palette.base(this, 'setHighlighted', highlight);
607
};
608
609
610
/**
611
* Private helper; selects or deselects the given item based on the value of
612
* the Boolean argument. This implementation simply applies selection styling
613
* to the cell containing the item to be selected. Does nothing if the palette
614
* hasn't been rendered yet.
615
* @param {Node} item Item to select or deselect.
616
* @param {boolean} select If true, the item is selected; otherwise it is
617
* deselected.
618
* @private
619
* @suppress {strictMissingProperties} Added to tighten compiler checks
620
*/
621
goog.ui.Palette.prototype.selectItem_ = function(item, select) {
622
'use strict';
623
if (this.getElement()) {
624
this.getRenderer().selectCell(this, item, select);
625
}
626
};
627
628
629
/**
630
* Calculates and updates the size of the palette based on any preset values
631
* and the number of palette items. If there is no preset size, sets the
632
* palette size to the smallest square big enough to contain all items. If
633
* there is a preset number of columns, increases the number of rows to hold
634
* all items if needed. (If there are too many rows, does nothing.)
635
* @private
636
*/
637
goog.ui.Palette.prototype.adjustSize_ = function() {
638
'use strict';
639
var items = this.getContent();
640
if (items) {
641
if (this.size_ && this.size_.width) {
642
// There is already a size set; honor the number of columns (if >0), but
643
// increase the number of rows if needed.
644
/**
645
* @suppress {strictMissingProperties} Added to tighten compiler checks
646
*/
647
var minRows = Math.ceil(items.length / this.size_.width);
648
if (typeof this.size_.height !== 'number' ||
649
this.size_.height < minRows) {
650
this.size_.height = minRows;
651
}
652
} else {
653
// No size has been set; size the grid to the smallest square big enough
654
// to hold all items (hey, why not?).
655
/**
656
* @suppress {strictMissingProperties} Added to tighten compiler checks
657
*/
658
var length = Math.ceil(Math.sqrt(items.length));
659
this.size_ = new goog.math.Size(length, length);
660
}
661
} else {
662
// No items; set size to 0x0.
663
this.size_ = new goog.math.Size(0, 0);
664
}
665
};
666
667
668
669
/**
670
* A component to represent the currently highlighted cell.
671
* @constructor
672
* @extends {goog.ui.Control}
673
* @private
674
*/
675
goog.ui.Palette.CurrentCell_ = function() {
676
'use strict';
677
goog.ui.Palette.CurrentCell_.base(this, 'constructor', null);
678
this.setDispatchTransitionEvents(goog.ui.Component.State.HOVER, true);
679
};
680
goog.inherits(goog.ui.Palette.CurrentCell_, goog.ui.Control);
681
682
683
/**
684
* @param {boolean} highlight Whether to highlight or unhighlight the component.
685
* @return {boolean} Whether it was successful.
686
*/
687
goog.ui.Palette.CurrentCell_.prototype.tryHighlight = function(highlight) {
688
'use strict';
689
this.setHighlighted(highlight);
690
return this.isHighlighted() == highlight;
691
};
692
693