1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | import onsElements from '../ons/elements.js';
|
19 | import util from '../ons/util.js';
|
20 | import styler from '../ons/styler.js';
|
21 | import platform from '../ons/platform.js';
|
22 | import BaseElement from './base/base-element.js';
|
23 | import GestureDetector from '../ons/gesture-detector.js';
|
24 | import animit from '../ons/animit.js';
|
25 |
|
26 | const STATE_INITIAL = 'initial';
|
27 | const STATE_PREACTION = 'preaction';
|
28 | const STATE_ACTION = 'action';
|
29 |
|
30 | const throwType = (el, type) => util.throw(`"${el}" must be ${type}`);
|
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 | export default class PullHookElement extends BaseElement {
|
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 | constructor() {
|
130 | super();
|
131 |
|
132 | this._onDrag = this._onDrag.bind(this);
|
133 | this._onDragStart = this._onDragStart.bind(this);
|
134 | this._onDragEnd = this._onDragEnd.bind(this);
|
135 | this._onScroll = this._onScroll.bind(this);
|
136 |
|
137 | this._setState(STATE_INITIAL, true);
|
138 | this._hide();
|
139 |
|
140 | const {onConnected, onDisconnected} = util.defineListenerProperty(this, 'pull');
|
141 | this._connectOnPull = onConnected;
|
142 | this._disconnectOnPull = onDisconnected;
|
143 | }
|
144 |
|
145 | _setStyle() {
|
146 | const height = this.height + 'px';
|
147 | styler(this, { height, lineHeight: height });
|
148 | this.style.display === '' && this._show();
|
149 | }
|
150 |
|
151 | _onScroll(event) {
|
152 | const element = this._pageElement;
|
153 |
|
154 | if (element.scrollTop < 0) {
|
155 | element.scrollTop = 0;
|
156 | }
|
157 | }
|
158 |
|
159 | _canConsumeGesture(gesture) {
|
160 | return gesture.direction === 'up' || gesture.direction === 'down';
|
161 | }
|
162 |
|
163 | _onDragStart(event) {
|
164 | if (!event.gesture || this.disabled) {
|
165 | return;
|
166 | }
|
167 |
|
168 | const tapY = event.gesture.center.clientY + this._pageElement.scrollTop;
|
169 | const maxY = window.innerHeight;
|
170 |
|
171 | const draggableAreaRatio = 1;
|
172 |
|
173 | this._ignoreDrag = event.consumed || (tapY > maxY * draggableAreaRatio);
|
174 |
|
175 | if (!this._ignoreDrag) {
|
176 | const consume = event.consume;
|
177 | event.consume = () => {
|
178 | consume && consume();
|
179 | this._ignoreDrag = true;
|
180 |
|
181 |
|
182 | this._hide();
|
183 | };
|
184 |
|
185 | if (this._canConsumeGesture(event.gesture)) {
|
186 | consume && consume();
|
187 | event.consumed = true;
|
188 | this._show();
|
189 | }
|
190 | }
|
191 |
|
192 | this._startScroll = this._pageElement.scrollTop;
|
193 | }
|
194 |
|
195 | _onDrag(event) {
|
196 | if (!event.gesture || this.disabled || this._ignoreDrag || !this._canConsumeGesture(event.gesture)) {
|
197 | return;
|
198 | }
|
199 |
|
200 |
|
201 | if (this.style.display === 'none') {
|
202 | this._show();
|
203 | }
|
204 |
|
205 | event.stopPropagation();
|
206 |
|
207 | const tapY = event.gesture.center.clientY + this._pageElement.scrollTop;
|
208 | const maxY = window.innerHeight;
|
209 |
|
210 | const scroll = Math.max(event.gesture.deltaY - this._startScroll, 0);
|
211 | if (scroll !== this._currentTranslation) {
|
212 |
|
213 | const th = this.thresholdHeight;
|
214 | if (th > 0 && scroll >= th) {
|
215 | event.gesture.stopDetect();
|
216 | setImmediate(() => this._finish());
|
217 |
|
218 | } else if (scroll >= this.height) {
|
219 | this._setState(STATE_PREACTION);
|
220 |
|
221 | } else {
|
222 | this._setState(STATE_INITIAL);
|
223 | }
|
224 |
|
225 | this._translateTo(scroll);
|
226 | }
|
227 | }
|
228 |
|
229 | _onDragEnd(event) {
|
230 | if (!event.gesture || this.disabled || this._ignoreDrag) {
|
231 | return;
|
232 | }
|
233 |
|
234 | event.stopPropagation();
|
235 |
|
236 | if (this._currentTranslation > 0) {
|
237 | const scroll = this._currentTranslation;
|
238 |
|
239 | if (scroll > this.height) {
|
240 | this._finish();
|
241 | } else {
|
242 | this._translateTo(0, {animate: true});
|
243 | }
|
244 | }
|
245 | }
|
246 |
|
247 | |
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | get onAction() {
|
255 | return this._onAction;
|
256 | }
|
257 |
|
258 | set onAction(value) {
|
259 | if (value && !(value instanceof Function)) {
|
260 | throwType('onAction', 'function or null');
|
261 | }
|
262 | this._onAction = value;
|
263 | }
|
264 |
|
265 | |
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 | _finish() {
|
274 | this._setState(STATE_ACTION);
|
275 | this._translateTo(this.height, {animate: true});
|
276 | const action = this.onAction || (done => done());
|
277 | action(() => {
|
278 | this._translateTo(0, {animate: true});
|
279 | this._setState(STATE_INITIAL);
|
280 | });
|
281 | }
|
282 |
|
283 | |
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | set height(value) {
|
291 | if (!util.isInteger(value)) {
|
292 | throwType('height', 'integer');
|
293 | }
|
294 |
|
295 | this.setAttribute('height', `${value}px`);
|
296 | }
|
297 |
|
298 | get height() {
|
299 | return parseInt(this.getAttribute('height') || '64', 10);
|
300 | }
|
301 |
|
302 | |
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | set thresholdHeight(value) {
|
310 | if (!util.isInteger(value)) {
|
311 | throwType('thresholdHeight', 'integer');
|
312 | }
|
313 |
|
314 | this.setAttribute('threshold-height', `${value}px`);
|
315 | }
|
316 |
|
317 | get thresholdHeight() {
|
318 | return parseInt(this.getAttribute('threshold-height') || '96', 10);
|
319 | }
|
320 |
|
321 | _setState(state, noEvent) {
|
322 | const lastState = this.state;
|
323 |
|
324 | this.setAttribute('state', state);
|
325 |
|
326 | if (!noEvent && lastState !== this.state) {
|
327 | util.triggerElementEvent(this, 'changestate', {
|
328 | pullHook: this,
|
329 | state: state,
|
330 | lastState: lastState
|
331 | });
|
332 | }
|
333 | }
|
334 |
|
335 | |
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 | get state() {
|
344 | return this.getAttribute('state');
|
345 | }
|
346 |
|
347 | |
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 | get pullDistance() {
|
356 | return this._currentTranslation;
|
357 | }
|
358 |
|
359 | |
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 | _show() {
|
368 |
|
369 | setImmediate(() => {
|
370 | this.style.display = '';
|
371 | if (this._pageElement) {
|
372 | this._pageElement.style.marginTop = `-${this.height}px`;
|
373 | }
|
374 | });
|
375 | }
|
376 |
|
377 | _hide() {
|
378 | this.style.display = 'none';
|
379 | if (this._pageElement) {
|
380 | this._pageElement.style.marginTop = '';
|
381 | }
|
382 | }
|
383 |
|
384 | |
385 |
|
386 |
|
387 |
|
388 |
|
389 | _translateTo(scroll, options = {}) {
|
390 | if (this._currentTranslation == 0 && scroll == 0) {
|
391 | return;
|
392 | }
|
393 |
|
394 | this._currentTranslation = scroll;
|
395 | const opt = options.animate ? { duration: .3, timing: 'cubic-bezier(.1, .7, .1, 1)' } : {};
|
396 | util.triggerElementEvent(this, 'pull', { ratio: (scroll / this.height).toFixed(2), animationOptions: opt });
|
397 | const scrollElement = this.hasAttribute('fixed-content') ? this : this._pageElement;
|
398 |
|
399 | animit(scrollElement)
|
400 | .queue({ transform: `translate3d(0px, ${scroll}px, 0px)` }, opt)
|
401 | .play(() => {
|
402 | scroll === 0 && styler.clear(scrollElement, 'transition transform');
|
403 | options.callback instanceof Function && options.callback();
|
404 | });
|
405 | }
|
406 |
|
407 | _disableDragLock() {
|
408 | this._dragLockDisabled = true;
|
409 | this._setupListeners(true);
|
410 | }
|
411 |
|
412 | _setupListeners(add) {
|
413 | const scrollToggle = action => this._pageElement[`${action}EventListener`]('scroll', this._onScroll, false);
|
414 | const gdToggle = action => {
|
415 | const passive = { passive: true };
|
416 | this._gestureDetector[action]('drag', this._onDrag, passive);
|
417 | this._gestureDetector[action]('dragstart', this._onDragStart, passive);
|
418 | this._gestureDetector[action]('dragend', this._onDragEnd, passive);
|
419 | };
|
420 |
|
421 | if (this._gestureDetector) {
|
422 | gdToggle('off');
|
423 | this._gestureDetector.dispose();
|
424 | this._gestureDetector = null;
|
425 | }
|
426 | scrollToggle('remove');
|
427 |
|
428 | if (add) {
|
429 | this._gestureDetector = new GestureDetector(this._pageElement, {
|
430 | dragMinDistance: 1,
|
431 | dragDistanceCorrection: false,
|
432 | dragLockToAxis: !this._dragLockDisabled,
|
433 | passive: true
|
434 | });
|
435 |
|
436 | gdToggle('on');
|
437 | scrollToggle('add');
|
438 | }
|
439 | }
|
440 |
|
441 | connectedCallback() {
|
442 | this._currentTranslation = 0;
|
443 | this._pageElement = this.parentNode;
|
444 |
|
445 | this._setupListeners(true);
|
446 | this._setStyle();
|
447 |
|
448 | this._connectOnPull();
|
449 | }
|
450 |
|
451 | disconnectedCallback() {
|
452 | this._hide();
|
453 | this._setupListeners(false);
|
454 |
|
455 | this._disconnectOnPull();
|
456 | }
|
457 |
|
458 | static get observedAttributes() {
|
459 | return ['height'];
|
460 | }
|
461 |
|
462 | attributeChangedCallback(name, last, current) {
|
463 | if (name === 'height' && this._pageElement) {
|
464 | this._setStyle();
|
465 | }
|
466 | }
|
467 |
|
468 | static get events() {
|
469 | return ['changestate', 'pull'];
|
470 | }
|
471 | }
|
472 |
|
473 | util.defineBooleanProperties(PullHookElement, ['disabled', 'fixed-content']);
|
474 |
|
475 | onsElements.PullHook = PullHookElement;
|
476 | customElements.define('ons-pull-hook', PullHookElement);
|