|
5 | 5 | * license that can be found in the LICENSE file.
|
6 | 6 | */
|
7 | 7 | -->
|
8 |
| -<element name="g-overlay" attributes="showing, timeout"> |
| 8 | +<element name="g-overlay" attributes="opened, timeout" |
| 9 | + handlers="click: clickHandler, keydown: keydownHandler, |
| 10 | + webkitAniamtionEnd: finishAnimate, webkitTransitionEnd: finishAnimate"> |
9 | 11 | <link rel="components" href="g-component.html">
|
| 12 | +<link rel="stylesheet" href="css/g-overlay.css"> |
10 | 13 | <template>
|
11 |
| - <style scoped> |
12 |
| - @host { |
13 |
| - position: absolute; |
14 |
| - z-index: 10; |
15 |
| - } |
16 |
| - </style> |
17 | 14 | <content></content>
|
18 | 15 | </template>
|
19 | 16 | <script>
|
| 17 | + // TODO(sorvell): promote this if it becomes a pattern |
| 18 | + var setBooleanAttribute = function(inNode, inName, inValue) { |
| 19 | + inNode[inValue ? 'setAttribute' : 'removeAttribute'](inName, ''); |
| 20 | + }; |
| 21 | + |
| 22 | + // TODO(sorvell): is there a more new-fangled way to do this? |
| 23 | + var makeFocusable = function(inNode) { |
| 24 | + if (!inNode.hasAttribute('tabIndex')) { |
| 25 | + inNode.setAttribute('tabIndex', -1); |
| 26 | + } |
| 27 | + } |
| 28 | + |
| 29 | + /** |
| 30 | + * The overlay component is hidden by default and can be opened to display |
| 31 | + * its content. It's common to animate an overlay opened and closed. This |
| 32 | + * can be achieved by styling the overlay node via the `opened` and |
| 33 | + * `animating` attributes. |
| 34 | + */ |
20 | 35 | this.component({
|
21 | 36 | shadowRootCreated: function() {
|
22 | 37 | this.hidden = true;
|
23 | 38 | },
|
| 39 | + created: function() { |
| 40 | + makeFocusable(this); |
| 41 | + }, |
24 | 42 | prototype: {
|
| 43 | + //* Timeout (ms) for animation. After timeout, any opening animation will |
| 44 | + //* be aborted and overlay will be set to opened or not and not animating. |
25 | 45 | timeout: 1000,
|
26 |
| - showingChanged: function() { |
27 |
| - this.animateShowing(); |
28 |
| - webkitRequestAnimationFrame(this.fireShowingChange.bind(this)); |
29 |
| - }, |
30 |
| - dispatch: function(inName, inDetail) { |
31 |
| - this.dispatchEvent(new CustomEvent(inName, { |
32 |
| - bubbles: true, |
33 |
| - detail: inDetail |
34 |
| - })); |
35 |
| - }, |
36 |
| - fireShowingChange: function() { |
37 |
| - this.dispatch("showingChange", { |
38 |
| - showing: this.showing, |
39 |
| - rect: this.getBoundingClientRect() |
40 |
| - }); |
| 46 | + openedChanged: function() { |
| 47 | + this.startAnimation(); |
| 48 | + // TODO(sorvell): need a controllable api for this, including |
| 49 | + // maybe focusElement. |
| 50 | + if (this.opened) { |
| 51 | + this.focus(); |
| 52 | + } |
| 53 | + this.fireEvent('openedChanged', {opened: this.opened}); |
41 | 54 | },
|
42 |
| - animateShowing: function() { |
| 55 | + startAnimation: function() { |
| 56 | + this.cancelAnimation(); |
43 | 57 | this.hidden = false;
|
44 | 58 | webkitRequestAnimationFrame(function() {
|
45 |
| - var setBooleanAttribute = function(inNode, inName, inValue) { |
46 |
| - inNode[inValue ? "setAttribute" : "removeAttribute"](inName, ""); |
47 |
| - }; |
48 |
| - setBooleanAttribute(this, "opening", this.showing); |
49 |
| - setBooleanAttribute(this, "closing", !this.showing); |
50 |
| - this.unlisten(); |
51 |
| - this.listen(); |
| 59 | + setBooleanAttribute(this, 'opened', this.opened); |
| 60 | + setBooleanAttribute(this, 'animating', true); |
| 61 | + this._animating = setTimeout(this.finishAnimate.bind(this), this.timeout); |
52 | 62 | }.bind(this));
|
53 | 63 | },
|
54 |
| - listen: function() { |
55 |
| - this.animationListener = this.finishAnimate.bind(this); |
56 |
| - this.addEventListener("webkitAnimationEnd", this.animationListener); |
57 |
| - this.addEventListener("webkitTransitionEnd", this.animationListener); |
58 |
| - // always finish animation within timeout |
59 |
| - this.jobId = this.utils.job(this.jobId, this.animationListener, this.timeout); |
60 |
| - }, |
61 | 64 | finishAnimate: function() {
|
62 |
| - if (!this.showing) { |
63 |
| - this.hidden = true; |
64 |
| - this.classList.remove(this.hideClass); |
| 65 | + if (this._animating) { |
| 66 | + this.cancelAnimation(); |
| 67 | + setBooleanAttribute(this, 'animating', false); |
| 68 | + if (!this.opened) { |
| 69 | + this.hidden = true; |
| 70 | + } |
65 | 71 | }
|
66 |
| - this.unlisten(); |
67 | 72 | },
|
68 |
| - unlisten: function() { |
69 |
| - this.removeEventListener("webkitAnimationEnd", this.animationListener); |
70 |
| - this.removeEventListener("webkitTransitionEnd", this.animationListener); |
71 |
| - this.utils.job.stop(this.jobId); |
| 73 | + cancelAnimation: function() { |
| 74 | + if (this._animating) { |
| 75 | + clearTimeout(this._animating); |
| 76 | + this._animating = null; |
| 77 | + } |
| 78 | + }, |
| 79 | + //* Toggle the opened state of the overlay. |
| 80 | + toggle: function() { |
| 81 | + this.opened = !this.opened; |
| 82 | + }, |
| 83 | + clickHandler: function(e) { |
| 84 | + if (e.target && e.target.hasAttribute('overlay-toggle')) { |
| 85 | + this.toggle(); |
| 86 | + } |
| 87 | + }, |
| 88 | + keydownHandler: function(e) { |
| 89 | + if (e.keyCode == 27) { |
| 90 | + this.opened = false; |
| 91 | + } |
72 | 92 | }
|
73 | 93 | }
|
74 | 94 | });
|
|
0 commit comments