Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(46)

Side by Side Diff: third_party/polymer/v1_0/components-chromium/iron-overlay-behavior/iron-overlay-behavior-extracted.js

Issue 1766433002: Roll Polymer to 1.3.1 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /** 1 /**
2 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays 2 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
3 on top of other content. It includes an optional backdrop, and can be used to im plement a variety 3 on top of other content. It includes an optional backdrop, and can be used to im plement a variety
4 of UI controls including dialogs and drop downs. Multiple overlays may be displa yed at once. 4 of UI controls including dialogs and drop downs. Multiple overlays may be displa yed at once.
5 5
6 ### Closing and canceling 6 ### Closing and canceling
7 7
8 A dialog may be hidden by closing or canceling. The difference between close and cancel is user 8 A dialog may be hidden by closing or canceling. The difference between close and cancel is user
9 intent. Closing generally implies that the user acknowledged the content on the overlay. By default, 9 intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
10 it will cancel whenever the user taps outside it or presses the escape key. This behavior is 10 it will cancel whenever the user taps outside it or presses the escape key. This behavior is
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after
93 93
94 /** 94 /**
95 * Returns the reason this dialog was last closed. 95 * Returns the reason this dialog was last closed.
96 */ 96 */
97 closingReason: { 97 closingReason: {
98 // was a getter before, but needs to be a property so other 98 // was a getter before, but needs to be a property so other
99 // behaviors can override this. 99 // behaviors can override this.
100 type: Object 100 type: Object
101 }, 101 },
102 102
103 /**
104 * The HTMLElement that will be firing relevant KeyboardEvents.
105 * Used for capturing esc and tab. Overridden from `IronA11yKeysBehavior`.
106 */
107 keyEventTarget: {
108 type: Object,
109 value: document
110 },
111
112 /**
113 * Set to true to enable restoring of focus when overlay is closed.
114 */
115 restoreFocusOnClose: {
116 type: Boolean,
117 value: false
118 },
119
103 _manager: { 120 _manager: {
104 type: Object, 121 type: Object,
105 value: Polymer.IronOverlayManager 122 value: Polymer.IronOverlayManager
106 }, 123 },
107 124
108 _boundOnCaptureClick: { 125 _boundOnCaptureClick: {
109 type: Function, 126 type: Function,
110 value: function() { 127 value: function() {
111 return this._onCaptureClick.bind(this); 128 return this._onCaptureClick.bind(this);
112 } 129 }
113 }, 130 },
114 131
115 _boundOnCaptureKeydown: {
116 type: Function,
117 value: function() {
118 return this._onCaptureKeydown.bind(this);
119 }
120 },
121
122 _boundOnCaptureFocus: { 132 _boundOnCaptureFocus: {
123 type: Function, 133 type: Function,
124 value: function() { 134 value: function() {
125 return this._onCaptureFocus.bind(this); 135 return this._onCaptureFocus.bind(this);
126 } 136 }
127 }, 137 },
128 138
129 /** @type {?Node} */ 139 /**
140 * The node being focused.
141 * @type {?Node}
142 */
130 _focusedChild: { 143 _focusedChild: {
131 type: Object 144 type: Object
132 } 145 }
133 146
134 }, 147 },
135 148
149 keyBindings: {
150 'esc': '__onEsc',
151 'tab': '__onTab'
152 },
153
136 listeners: { 154 listeners: {
137 'iron-resize': '_onIronResize' 155 'iron-resize': '_onIronResize'
138 }, 156 },
139 157
140 /** 158 /**
141 * The backdrop element. 159 * The backdrop element.
142 * @type Node 160 * @type {Node}
143 */ 161 */
144 get backdropElement() { 162 get backdropElement() {
145 return this._manager.backdropElement; 163 return this._manager.backdropElement;
146 }, 164 },
147 165
166 /**
167 * Returns the node to give focus to.
168 * @type {Node}
169 */
148 get _focusNode() { 170 get _focusNode() {
149 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this; 171 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this;
150 }, 172 },
151 173
174 /**
175 * Array of nodes that can receive focus (overlay included), ordered by `tab index`.
176 * This is used to retrieve which is the first and last focusable nodes in o rder
177 * to wrap the focus for overlays `with-backdrop`.
178 *
179 * If you know what is your content (specifically the first and last focusab le children),
180 * you can override this method to return only `[firstFocusable, lastFocusab le];`
181 * @type {[Node]}
182 * @protected
183 */
184 get _focusableNodes() {
185 // Elements that can be focused even if they have [disabled] attribute.
186 var FOCUSABLE_WITH_DISABLED = [
187 'a[href]',
188 'area[href]',
189 'iframe',
190 '[tabindex]',
191 '[contentEditable=true]'
192 ];
193
194 // Elements that cannot be focused if they have [disabled] attribute.
195 var FOCUSABLE_WITHOUT_DISABLED = [
196 'input',
197 'select',
198 'textarea',
199 'button'
200 ];
201
202 // Discard elements with tabindex=-1 (makes them not focusable).
203 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') +
204 ':not([tabindex="-1"]),' +
205 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),' ) +
206 ':not([disabled]):not([tabindex="-1"])';
207
208 var focusables = Polymer.dom(this).querySelectorAll(selector);
209 if (this.tabIndex >= 0) {
210 // Insert at the beginning because we might have all elements with tabIn dex = 0,
211 // and the overlay should be the first of the list.
212 focusables.splice(0, 0, this);
213 }
214 // Sort by tabindex.
215 return focusables.sort(function (a, b) {
216 if (a.tabIndex === b.tabIndex) {
217 return 0;
218 }
219 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) {
220 return 1;
221 }
222 return -1;
223 });
224 },
225
152 ready: function() { 226 ready: function() {
153 // with-backdrop need tabindex to be set in order to trap the focus. 227 // with-backdrop needs tabindex to be set in order to trap the focus.
154 // If it is not set, IronOverlayBehavior will set it, and remove it if wit h-backdrop = false. 228 // If it is not set, IronOverlayBehavior will set it, and remove it if wit h-backdrop = false.
155 this.__shouldRemoveTabIndex = false; 229 this.__shouldRemoveTabIndex = false;
230 // Used for wrapping the focus on TAB / Shift+TAB.
231 this.__firstFocusableNode = this.__lastFocusableNode = null;
156 this._ensureSetup(); 232 this._ensureSetup();
157 }, 233 },
158 234
159 attached: function() { 235 attached: function() {
160 // Call _openedChanged here so that position can be computed correctly. 236 // Call _openedChanged here so that position can be computed correctly.
161 if (this._callOpenedWhenReady) { 237 if (this.opened) {
162 this._openedChanged(); 238 this._openedChanged();
163 } 239 }
240 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange);
164 }, 241 },
165 242
166 detached: function() { 243 detached: function() {
244 Polymer.dom(this).unobserveNodes(this._observer);
245 this._observer = null;
167 this.opened = false; 246 this.opened = false;
168 this._manager.trackBackdrop(this); 247 this._manager.trackBackdrop(this);
169 this._manager.removeOverlay(this); 248 this._manager.removeOverlay(this);
170 }, 249 },
171 250
172 /** 251 /**
173 * Toggle the opened state of the overlay. 252 * Toggle the opened state of the overlay.
174 */ 253 */
175 toggle: function() { 254 toggle: function() {
176 this._setCanceled(false); 255 this._setCanceled(false);
(...skipping 11 matching lines...) Expand all
188 /** 267 /**
189 * Close the overlay. 268 * Close the overlay.
190 */ 269 */
191 close: function() { 270 close: function() {
192 this._setCanceled(false); 271 this._setCanceled(false);
193 this.opened = false; 272 this.opened = false;
194 }, 273 },
195 274
196 /** 275 /**
197 * Cancels the overlay. 276 * Cancels the overlay.
277 * @param {?Event} event The original event
198 */ 278 */
199 cancel: function() { 279 cancel: function(event) {
200 var cancelEvent = this.fire('iron-overlay-canceled', undefined, {cancelabl e: true}); 280 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t rue});
201 if (cancelEvent.defaultPrevented) { 281 if (cancelEvent.defaultPrevented) {
202 return; 282 return;
203 } 283 }
204 284
205 this._setCanceled(true); 285 this._setCanceled(true);
206 this.opened = false; 286 this.opened = false;
207 }, 287 },
208 288
209 _ensureSetup: function() { 289 _ensureSetup: function() {
210 if (this._overlaySetup) { 290 if (this._overlaySetup) {
211 return; 291 return;
212 } 292 }
213 this._overlaySetup = true; 293 this._overlaySetup = true;
214 this.style.outline = 'none'; 294 this.style.outline = 'none';
215 this.style.display = 'none'; 295 this.style.display = 'none';
216 }, 296 },
217 297
218 _openedChanged: function() { 298 _openedChanged: function() {
219 if (this.opened) { 299 if (this.opened) {
220 this.removeAttribute('aria-hidden'); 300 this.removeAttribute('aria-hidden');
221 } else { 301 } else {
222 this.setAttribute('aria-hidden', 'true'); 302 this.setAttribute('aria-hidden', 'true');
223 Polymer.dom(this).unobserveNodes(this._observer);
224 } 303 }
225 304
226 // wait to call after ready only if we're initially open 305 // wait to call after ready only if we're initially open
227 if (!this._overlaySetup) { 306 if (!this._overlaySetup) {
228 this._callOpenedWhenReady = this.opened;
229 return; 307 return;
230 } 308 }
231 309
232 this._manager.trackBackdrop(this); 310 this._manager.trackBackdrop(this);
233 311
234 if (this.opened) { 312 if (this.opened) {
235 this._prepareRenderOpened(); 313 this._prepareRenderOpened();
236 } 314 }
237 315
238 if (this._openChangedAsync) { 316 if (this._openChangedAsync) {
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
293 node.addEventListener(event, boundListener, capture); 371 node.addEventListener(event, boundListener, capture);
294 } else { 372 } else {
295 // disable document-wide tap recognizer 373 // disable document-wide tap recognizer
296 if (event === 'tap') { 374 if (event === 'tap') {
297 Polymer.Gestures.remove(document, 'tap', null); 375 Polymer.Gestures.remove(document, 'tap', null);
298 } 376 }
299 node.removeEventListener(event, boundListener, capture); 377 node.removeEventListener(event, boundListener, capture);
300 } 378 }
301 }, 379 },
302 380
303 _toggleListeners: function () { 381 _toggleListeners: function() {
304 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureCli ck, true); 382 this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureCli ck, true);
305 this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptur eKeydown, true);
306 this._toggleListener(this.opened, document, 'focus', this._boundOnCaptureF ocus, true); 383 this._toggleListener(this.opened, document, 'focus', this._boundOnCaptureF ocus, true);
307 }, 384 },
308 385
309 // tasks which must occur before opening; e.g. making the element visible 386 // tasks which must occur before opening; e.g. making the element visible
310 _prepareRenderOpened: function() { 387 _prepareRenderOpened: function() {
388
311 this._manager.addOverlay(this); 389 this._manager.addOverlay(this);
312 390
391 // Needed to calculate the size of the overlay so that transitions on its size
392 // will have the correct starting points.
313 this._preparePositioning(); 393 this._preparePositioning();
314 this.fit(); 394 this.fit();
315 this._finishPositioning(); 395 this._finishPositioning();
316 396
317 if (this.withBackdrop) { 397 if (this.withBackdrop) {
318 this.backdropElement.prepare(); 398 this.backdropElement.prepare();
319 } 399 }
400
401 // Safari will apply the focus to the autofocus element when displayed for the first time,
402 // so we blur it. Later, _applyFocus will set the focus if necessary.
403 if (this.noAutoFocus && document.activeElement === this._focusNode) {
404 this._focusNode.blur();
405 }
320 }, 406 },
321 407
322 // tasks which cause the overlay to actually open; typically play an 408 // tasks which cause the overlay to actually open; typically play an
323 // animation 409 // animation
324 _renderOpened: function() { 410 _renderOpened: function() {
325 if (this.withBackdrop) { 411 if (this.withBackdrop) {
326 this.backdropElement.open(); 412 this.backdropElement.open();
327 } 413 }
328 this._finishRenderOpened(); 414 this._finishRenderOpened();
329 }, 415 },
330 416
331 _renderClosed: function() { 417 _renderClosed: function() {
332 if (this.withBackdrop) { 418 if (this.withBackdrop) {
333 this.backdropElement.close(); 419 this.backdropElement.close();
334 } 420 }
335 this._finishRenderClosed(); 421 this._finishRenderClosed();
336 }, 422 },
337 423
338 _finishRenderOpened: function() { 424 _finishRenderOpened: function() {
339 // focus the child node with [autofocus] 425 // This ensures the overlay is visible before we set the focus
426 // (by calling _onIronResize -> refit).
427 this.notifyResize();
428 // Focus the child node with [autofocus]
340 this._applyFocus(); 429 this._applyFocus();
341 430
342 this._observer = Polymer.dom(this).observeNodes(this.notifyResize);
343 this.fire('iron-overlay-opened'); 431 this.fire('iron-overlay-opened');
344 }, 432 },
345 433
346 _finishRenderClosed: function() { 434 _finishRenderClosed: function() {
347 // hide the overlay and remove the backdrop 435 // Hide the overlay and remove the backdrop.
348 this.resetFit(); 436 this.resetFit();
349 this.style.display = 'none'; 437 this.style.display = 'none';
350 this._manager.removeOverlay(this); 438 this._manager.removeOverlay(this);
351 439
352 this._focusedChild = null;
353 this._applyFocus(); 440 this._applyFocus();
441 this.notifyResize();
354 442
355 this.notifyResize();
356 this.fire('iron-overlay-closed', this.closingReason); 443 this.fire('iron-overlay-closed', this.closingReason);
357 }, 444 },
358 445
359 _preparePositioning: function() { 446 _preparePositioning: function() {
360 this.style.transition = this.style.webkitTransition = 'none'; 447 this.style.transition = this.style.webkitTransition = 'none';
361 this.style.transform = this.style.webkitTransform = 'none'; 448 this.style.transform = this.style.webkitTransform = 'none';
362 this.style.display = ''; 449 this.style.display = '';
363 }, 450 },
364 451
365 _finishPositioning: function() { 452 _finishPositioning: function() {
366 this.style.display = 'none'; 453 this.style.display = 'none';
367 this.style.transform = this.style.webkitTransform = ''; 454 this.style.transform = this.style.webkitTransform = '';
368 // force layout to avoid application of transform 455 // Force layout layout to avoid application of transform.
369 /** @suppress {suspiciousCode} */ this.offsetWidth; 456 // Set offsetWidth to itself so that compilers won't remove it.
457 this.offsetWidth = this.offsetWidth;
370 this.style.transition = this.style.webkitTransition = ''; 458 this.style.transition = this.style.webkitTransition = '';
371 }, 459 },
372 460
373 _applyFocus: function() { 461 _applyFocus: function() {
374 if (this.opened) { 462 if (this.opened) {
375 if (!this.noAutoFocus) { 463 if (!this.noAutoFocus) {
376 this._focusNode.focus(); 464 this._focusNode.focus();
377 } 465 }
378 } else { 466 } else {
379 this._focusNode.blur(); 467 this._focusNode.blur();
468 this._focusedChild = null;
380 this._manager.focusOverlay(); 469 this._manager.focusOverlay();
381 } 470 }
382 }, 471 },
383 472
384 _onCaptureClick: function(event) { 473 _onCaptureClick: function(event) {
385 if (this._manager.currentOverlay() === this && 474 if (this._manager.currentOverlay() === this &&
386 Polymer.dom(event).path.indexOf(this) === -1) { 475 Polymer.dom(event).path.indexOf(this) === -1) {
387 if (this.noCancelOnOutsideClick) { 476 if (this.noCancelOnOutsideClick) {
388 this._applyFocus(); 477 this._applyFocus();
389 } else { 478 } else {
390 this.cancel(); 479 this.cancel(event);
391 } 480 }
392 } 481 }
393 }, 482 },
394 483
395 _onCaptureKeydown: function(event) {
396 var ESC = 27;
397 if (this._manager.currentOverlay() === this &&
398 !this.noCancelOnEscKey &&
399 event.keyCode === ESC) {
400 this.cancel();
401 }
402 },
403
404 _onCaptureFocus: function (event) { 484 _onCaptureFocus: function (event) {
405 if (this._manager.currentOverlay() === this && 485 if (this._manager.currentOverlay() === this && this.withBackdrop) {
406 this.withBackdrop) {
407 var path = Polymer.dom(event).path; 486 var path = Polymer.dom(event).path;
408 if (path.indexOf(this) === -1) { 487 if (path.indexOf(this) === -1) {
409 event.stopPropagation(); 488 event.stopPropagation();
410 this._applyFocus(); 489 this._applyFocus();
411 } else { 490 } else {
412 this._focusedChild = path[0]; 491 this._focusedChild = path[0];
413 } 492 }
414 } 493 }
415 }, 494 },
416 495
417 _onIronResize: function() { 496 _onIronResize: function() {
418 if (this.opened) { 497 if (this.opened) {
419 this.refit(); 498 this.refit();
420 } 499 }
500 },
501
502 /**
503 * @protected
504 * Will call notifyResize if overlay is opened.
505 * Can be overridden in order to avoid multiple observers on the same node.
506 */
507 _onNodesChange: function() {
508 if (this.opened) {
509 this.notifyResize();
510 }
511 // Store it so we don't query too much.
512 var focusableNodes = this._focusableNodes;
513 this.__firstFocusableNode = focusableNodes[0];
514 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1];
515 },
516
517 __onEsc: function(event) {
518 // Not opened or not on top, so return.
519 if (this._manager.currentOverlay() !== this) {
520 return;
521 }
522 if (!this.noCancelOnEscKey) {
523 this.cancel(event);
524 }
525 },
526
527 __onTab: function(event) {
528 // Not opened or not on top, so return.
529 if (this._manager.currentOverlay() !== this) {
530 return;
531 }
532 // TAB wraps from last to first focusable.
533 // Shift + TAB wraps from first to last focusable.
534 var shift = event.detail.keyboardEvent.shiftKey;
535 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable Node;
536 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo de;
537 if (this.withBackdrop && this._focusedChild === nodeToCheck) {
538 // We set here the _focusedChild so that _onCaptureFocus will handle the
539 // wrapping of the focus (the next event after tab is focus).
540 this._focusedChild = nodeToSet;
541 }
421 } 542 }
422
423 /**
424 * Fired after the `iron-overlay` opens.
425 * @event iron-overlay-opened
426 */
427
428 /**
429 * Fired when the `iron-overlay` is canceled, but before it is closed.
430 * Cancel the event to prevent the `iron-overlay` from closing.
431 * @event iron-overlay-canceled
432 */
433
434 /**
435 * Fired after the `iron-overlay` closes.
436 * @event iron-overlay-closed
437 * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute
438 */
439 }; 543 };
440 544
441 /** @polymerBehavior */ 545 /** @polymerBehavior */
442 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB ehavior, Polymer.IronOverlayBehaviorImpl]; 546 Polymer.IronOverlayBehavior = [Polymer.IronA11yKeysBehavior, Polymer.IronFitBe havior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];
547
548 /**
549 * Fired after the `iron-overlay` opens.
550 * @event iron-overlay-opened
551 */
552
553 /**
554 * Fired when the `iron-overlay` is canceled, but before it is closed.
555 * Cancel the event to prevent the `iron-overlay` from closing.
556 * @event iron-overlay-canceled
557 * @param {Event} event The closing of the `iron-overlay` can be prevented
558 * by calling `event.preventDefault()`. The `event.detail` is the original even t that originated
559 * the canceling (e.g. ESC keyboard event or click event outside the `iron-over lay`).
560 */
561
562 /**
563 * Fired after the `iron-overlay` closes.
564 * @event iron-overlay-closed
565 * @param {{canceled: (boolean|undefined)}} closingReason Contains `canceled` ( whether the overlay was canceled).
566 */
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698