paper-fab-morph.html
7.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
<link rel="import" href="../iron-dropdown/iron-dropdown.html">
<!--
`paper-fab-morph` can be used to wrap a floating action button and another
element which is initially hidden, and when tapping the button, it will appear
as if the button is morphing into the other element, which appears in its place.
This element expects its content to contain two children: one with the class
`dropdown-trigger` , which is initially visible and acts as the trigger, and
another one with the class `dropdown-content` , which will be hidden until the
trigger is tapped.
Example:
<paper-fab-morph>
<paper-fab icon="menu" class="dropdown-trigger">
<paper-material class="dropdown-content">
<paper-menu>
<paper-item>One</paper-item>
<paper-item>Two</paper-item>
</paper-menu>
</paper-mterial>
</paper-fab-morph>
In the example above, the menu will be wrapped by an `iron-dropdown` element
and will be positioned relative to the button. Positioning can be modified by
setting the `horizontalAlign`, `verticalAlign`, `horizontalOffset` and
`verticalOffset` properties.
Alternatively, it's possible to set content element with fixed position, which
nullifies the dropdown positioning. This is useful for morphing into toolbars
and full screen elements for example.
It is also possible to use an element which implements overlay behavior as the
content, instead of having it wrapped with an `iron-dropdown`. In this case, the
`isOverlayContent` property should be set to true.
Example:
<paper-fab-morph is-overlay-content>
<paper-fab icon="create" class="dropdown-trigger">
<paper-dialog class="dropdown-content">
<div>Dialog</div>
<paper-dialog>
</paper-fab-morph>
### Styling
The following custom properties and mixins are also available for styling:
Custom property | Description | Default
----------------|-------------|----------
`--paper-morph-background` | Background color of the morphing element | `#fff`
`--paper-morph-dropdown` | Mixin applied to the `iron-dropdown` | `{}`
`--paper-morph-content` | Mixin applied to the dropdown's content | `{}`
@hero hero.svg
@demo demo/index.html
-->
<dom-module id="paper-fab-morph">
<template>
<style>
iron-dropdown {
@apply(--paper-morph-dropdown);
}
.dropdown-content {
@apply(--paper-morph-content);
}
#morpher {
position: fixed;
display: none;
background-color: var(--paper-morph-background, #fff);
}
</style>
<content id="fabContainer" select=".dropdown-trigger"></content>
<content id="contentContainer" select=".dropdown-content"></content>
<paper-material id="morpher"></paper-material>
</template>
</dom-module>
<script>
(function(Polymer) {
Polymer({
is: 'paper-fab-morph',
properties: {
/**
* Whether the content already has overlay behavior.
* If false, it will be wrapped by an iron-dropdown element, which can be
* configured with `horizontalAlign`, `verticalAlign`, `horizontalOffset`
* and `verticalOffset` properties.
*/
isOverlayContent: {
type: Boolean,
value: false
},
/**
* The transition duration in milliseconds.
*/
duration: {
type: Number,
value: 200
},
/**
* The orientation against which to align the dropdown
* horizontally relative to the trigger button.
*/
horizontalAlign: {
type: String,
value: 'left',
reflectToAttribute: true
},
/**
* The orientation against which to align the dropdown
* vertically relative to the trigger button.
*/
verticalAlign: {
type: String,
value: 'top',
reflectToAttribute: true
},
/**
* A pixel value that will be added to the position calculated for the
* given `horizontalAlign`. Use a negative value to offset to the
* left, or a positive value to offset to the right.
*/
horizontalOffset: {
type: Number,
value: 0,
notify: true
},
/**
* A pixel value that will be added to the position calculated for the
* given `verticalAlign`. Use a negative value to offset towards the
* top, or a positive value to offset towards the bottom.
*/
verticalOffset: {
type: Number,
value: 0,
notify: true
}
},
observers: [
'_updateOverlayPosition(verticalAlign, horizontalAlign, verticalOffset, horizontalOffset)'
],
ready: function() {
this._fab = Polymer.dom(this.$.fabContainer).getDistributedNodes()[0];
this._content = Polymer.dom(this.$.contentContainer).getDistributedNodes()[0];
if(this.isOverlayContent) {
this._fab.addEventListener('tap', function() {
this._content.open();
}.bind(this));
this._overlay = this._content;
} else {
var dropdown = document.createElement('iron-dropdown');
Polymer.dom(dropdown).appendChild(this._content);
Polymer.dom(this.root).appendChild(dropdown);
this._overlay = dropdown;
this._dropdown = dropdown;
this._fab.addEventListener('tap', function() {
this._dropdown.open();
}.bind(this));
this._updateOverlayPosition(this.verticalAlign, this.horizontalAlign, this.verticalOffset, this.horizontalOffset);
}
this._overlay.addEventListener('iron-overlay-opened', function() {
this._morphOpen();
}.bind(this));
this._overlay.addEventListener('iron-overlay-closed', function() {
this._morphClose();
}.bind(this));
},
/**
* Show the content.
*/
open: function() {
this._overlay.open();
},
/**
* Hide the content.
*/
close: function() {
this._overlay.close();
},
_updateOverlayPosition: function(verticalAlign, horizontalAlign, verticalOffset, horizontalOffset) {
if(this._dropdown) {
var d = this._dropdown;
d.verticalAlign = verticalAlign;
d.horizontalAlign = horizontalAlign;
d.verticalOffset = verticalOffset;
d.horizontalOffset = horizontalOffset;
}
},
_morphOpen: function() {
var fab = this._fab;
var content = this._content;
var fabRect = fab.getBoundingClientRect();
var morpher = this.$.morpher;
var ms = morpher.style;
ms.display = 'block';
ms.top = fabRect.top + 'px';
ms.left = fabRect.left + 'px';
ms.width = fabRect.width + 'px';
ms.height = fabRect.height + 'px';
ms.borderRadius = '50%';
ms.transitionDuration = this.duration + 'ms';
fab.style.visibility = 'hidden';
content.style.visibility = 'hidden';
var contentRect = content.getBoundingClientRect();
ms.top = contentRect.top + 'px';
ms.left = contentRect.left + 'px';
ms.width = contentRect.width + 'px';
ms.height = contentRect.height + 'px';
ms.borderRadius = '';
this.async(function() {
morpher.style.display = 'none';
content.style.visibility = 'visible';
}, this.duration);
},
_morphClose: function() {
var fab = this._fab;
var content = this._content;
var contentRect = fab.getBoundingClientRect();
var morpher = this.$.morpher;
var ms = morpher.style;
morpher.style.display = 'block';
this.async(function() {
var fabRect = fab.getBoundingClientRect();
ms.top = fabRect.top + 'px';
ms.left = fabRect.left + 'px';
ms.width = fabRect.width + 'px';
ms.height = fabRect.height + 'px';
ms.borderRadius = '50%';
this.async(function() {
morpher.style.display = 'none';
fab.style.visibility = 'visible';
}, this.duration);
});
}
});
})(Polymer);
</script>