UI for Zipcoin Blue

picker-column.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. import { Component, ElementRef, EventEmitter, Input, NgZone, Output, ViewChild } from '@angular/core';
  2. import { clamp } from '../../util/util';
  3. import { Config } from '../../config/config';
  4. import { DomController } from '../../platform/dom-controller';
  5. import { Haptic } from '../../tap-click/haptic';
  6. import { DECELERATION_FRICTION, FRAME_MS, MAX_PICKER_SPEED, PICKER_OPT_SELECTED } from './picker-options';
  7. import { Platform } from '../../platform/platform';
  8. import { pointerCoord } from '../../util/dom';
  9. import { UIEventManager } from '../../gestures/ui-event-manager';
  10. /**
  11. * @hidden
  12. */
  13. var PickerColumnCmp = (function () {
  14. function PickerColumnCmp(config, _plt, elementRef, _zone, _haptic, plt, domCtrl) {
  15. this._plt = _plt;
  16. this.elementRef = elementRef;
  17. this._zone = _zone;
  18. this._haptic = _haptic;
  19. this.y = 0;
  20. this.pos = [];
  21. this.startY = null;
  22. this.ionChange = new EventEmitter();
  23. this.events = new UIEventManager(plt);
  24. this.rotateFactor = config.getNumber('pickerRotateFactor', 0);
  25. this.scaleFactor = config.getNumber('pickerScaleFactor', 1);
  26. this.decelerateFunc = this.decelerate.bind(this);
  27. this.debouncer = domCtrl.debouncer();
  28. }
  29. PickerColumnCmp.prototype.ngAfterViewInit = function () {
  30. // get the scrollable element within the column
  31. var colEle = this.colEle.nativeElement;
  32. this.colHeight = colEle.clientHeight;
  33. // get the height of one option
  34. this.optHeight = (colEle.firstElementChild ? colEle.firstElementChild.clientHeight : 0);
  35. // Listening for pointer events
  36. this.events.pointerEvents({
  37. element: this.elementRef.nativeElement,
  38. pointerDown: this.pointerStart.bind(this),
  39. pointerMove: this.pointerMove.bind(this),
  40. pointerUp: this.pointerEnd.bind(this),
  41. capture: true,
  42. zone: false
  43. });
  44. };
  45. PickerColumnCmp.prototype.ngOnDestroy = function () {
  46. this._plt.cancelRaf(this.rafId);
  47. this.events.destroy();
  48. };
  49. PickerColumnCmp.prototype.pointerStart = function (ev) {
  50. (void 0) /* console.debug */;
  51. this._haptic.gestureSelectionStart();
  52. // We have to prevent default in order to block scrolling under the picker
  53. // but we DO NOT have to stop propagation, since we still want
  54. // some "click" events to capture
  55. ev.preventDefault();
  56. // cancel any previous raf's that haven't fired yet
  57. this._plt.cancelRaf(this.rafId);
  58. // remember where the pointer started from`
  59. this.startY = pointerCoord(ev).y;
  60. // reset everything
  61. this.velocity = 0;
  62. this.pos.length = 0;
  63. this.pos.push(this.startY, Date.now());
  64. var options = this.col.options;
  65. var minY = (options.length - 1);
  66. var maxY = 0;
  67. for (var i = 0; i < options.length; i++) {
  68. if (!options[i].disabled) {
  69. minY = Math.min(minY, i);
  70. maxY = Math.max(maxY, i);
  71. }
  72. }
  73. this.minY = (minY * this.optHeight * -1);
  74. this.maxY = (maxY * this.optHeight * -1);
  75. return true;
  76. };
  77. PickerColumnCmp.prototype.pointerMove = function (ev) {
  78. var _this = this;
  79. ev.preventDefault();
  80. ev.stopPropagation();
  81. var currentY = pointerCoord(ev).y;
  82. this.pos.push(currentY, Date.now());
  83. this.debouncer.write(function () {
  84. if (_this.startY === null) {
  85. return;
  86. }
  87. // update the scroll position relative to pointer start position
  88. var y = _this.y + (currentY - _this.startY);
  89. if (y > _this.minY) {
  90. // scrolling up higher than scroll area
  91. y = Math.pow(y, 0.8);
  92. _this.bounceFrom = y;
  93. }
  94. else if (y < _this.maxY) {
  95. // scrolling down below scroll area
  96. y += Math.pow(_this.maxY - y, 0.9);
  97. _this.bounceFrom = y;
  98. }
  99. else {
  100. _this.bounceFrom = 0;
  101. }
  102. _this.update(y, 0, false, false);
  103. var currentIndex = Math.max(Math.abs(Math.round(y / _this.optHeight)), 0);
  104. if (currentIndex !== _this.lastTempIndex) {
  105. // Trigger a haptic event for physical feedback that the index has changed
  106. _this._haptic.gestureSelectionChanged();
  107. _this.lastTempIndex = currentIndex;
  108. }
  109. });
  110. };
  111. PickerColumnCmp.prototype.pointerEnd = function (ev) {
  112. ev.preventDefault();
  113. this.debouncer.cancel();
  114. if (this.startY === null) {
  115. return;
  116. }
  117. (void 0) /* console.debug */;
  118. this.velocity = 0;
  119. if (this.bounceFrom > 0) {
  120. // bounce back up
  121. this.update(this.minY, 100, true, true);
  122. return;
  123. }
  124. else if (this.bounceFrom < 0) {
  125. // bounce back down
  126. this.update(this.maxY, 100, true, true);
  127. return;
  128. }
  129. var endY = pointerCoord(ev).y;
  130. this.pos.push(endY, Date.now());
  131. var endPos = (this.pos.length - 1);
  132. var startPos = endPos;
  133. var timeRange = (Date.now() - 100);
  134. // move pointer to position measured 100ms ago
  135. for (var i = endPos; i > 0 && this.pos[i] > timeRange; i -= 2) {
  136. startPos = i;
  137. }
  138. if (startPos !== endPos) {
  139. // compute relative movement between these two points
  140. var timeOffset = (this.pos[endPos] - this.pos[startPos]);
  141. var movedTop = (this.pos[startPos - 1] - this.pos[endPos - 1]);
  142. // based on XXms compute the movement to apply for each render step
  143. var velocity = ((movedTop / timeOffset) * FRAME_MS);
  144. this.velocity = clamp(-MAX_PICKER_SPEED, velocity, MAX_PICKER_SPEED);
  145. }
  146. if (Math.abs(endY - this.startY) > 3) {
  147. var y = this.y + (endY - this.startY);
  148. this.update(y, 0, true, true);
  149. }
  150. this.startY = null;
  151. this.decelerate();
  152. };
  153. PickerColumnCmp.prototype.decelerate = function () {
  154. var y = 0;
  155. if (isNaN(this.y) || !this.optHeight) {
  156. // fallback in case numbers get outta wack
  157. this.update(y, 0, true, true);
  158. this._haptic.gestureSelectionEnd();
  159. }
  160. else if (Math.abs(this.velocity) > 0) {
  161. // still decelerating
  162. this.velocity *= DECELERATION_FRICTION;
  163. // do not let it go slower than a velocity of 1
  164. this.velocity = (this.velocity > 0)
  165. ? Math.max(this.velocity, 1)
  166. : Math.min(this.velocity, -1);
  167. y = Math.round(this.y - this.velocity);
  168. if (y > this.minY) {
  169. // whoops, it's trying to scroll up farther than the options we have!
  170. y = this.minY;
  171. this.velocity = 0;
  172. }
  173. else if (y < this.maxY) {
  174. // gahh, it's trying to scroll down farther than we can!
  175. y = this.maxY;
  176. this.velocity = 0;
  177. }
  178. var notLockedIn = (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1);
  179. this.update(y, 0, true, !notLockedIn);
  180. if (notLockedIn) {
  181. // isn't locked in yet, keep decelerating until it is
  182. this.rafId = this._plt.raf(this.decelerateFunc);
  183. }
  184. }
  185. else if (this.y % this.optHeight !== 0) {
  186. // needs to still get locked into a position so options line up
  187. var currentPos = Math.abs(this.y % this.optHeight);
  188. // create a velocity in the direction it needs to scroll
  189. this.velocity = (currentPos > (this.optHeight / 2) ? 1 : -1);
  190. this._haptic.gestureSelectionEnd();
  191. this.decelerate();
  192. }
  193. var currentIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0);
  194. if (currentIndex !== this.lastTempIndex) {
  195. // Trigger a haptic event for physical feedback that the index has changed
  196. this._haptic.gestureSelectionChanged();
  197. }
  198. this.lastTempIndex = currentIndex;
  199. };
  200. PickerColumnCmp.prototype.optClick = function (ev, index) {
  201. if (!this.velocity) {
  202. ev.preventDefault();
  203. ev.stopPropagation();
  204. this.setSelected(index, 150);
  205. }
  206. };
  207. PickerColumnCmp.prototype.setSelected = function (selectedIndex, duration) {
  208. // if there is a selected index, then figure out it's y position
  209. // if there isn't a selected index, then just use the top y position
  210. var y = (selectedIndex > -1) ? ((selectedIndex * this.optHeight) * -1) : 0;
  211. this._plt.cancelRaf(this.rafId);
  212. this.velocity = 0;
  213. // so what y position we're at
  214. this.update(y, duration, true, true);
  215. };
  216. PickerColumnCmp.prototype.update = function (y, duration, saveY, emitChange) {
  217. // ensure we've got a good round number :)
  218. y = Math.round(y);
  219. var i;
  220. var button;
  221. var opt;
  222. var optOffset;
  223. var visible;
  224. var translateX;
  225. var translateY;
  226. var translateZ;
  227. var rotateX;
  228. var transform;
  229. var selected;
  230. var parent = this.colEle.nativeElement;
  231. var children = parent.children;
  232. var length = children.length;
  233. var selectedIndex = this.col.selectedIndex = Math.min(Math.max(Math.round(-y / this.optHeight), 0), length - 1);
  234. var durationStr = (duration === 0) ? null : duration + 'ms';
  235. var scaleStr = "scale(" + this.scaleFactor + ")";
  236. for (i = 0; i < length; i++) {
  237. button = children[i];
  238. opt = this.col.options[i];
  239. optOffset = (i * this.optHeight) + y;
  240. visible = true;
  241. transform = '';
  242. if (this.rotateFactor !== 0) {
  243. rotateX = optOffset * this.rotateFactor;
  244. if (Math.abs(rotateX) > 90) {
  245. visible = false;
  246. }
  247. else {
  248. translateX = 0;
  249. translateY = 0;
  250. translateZ = 90;
  251. transform = "rotateX(" + rotateX + "deg) ";
  252. }
  253. }
  254. else {
  255. translateX = 0;
  256. translateZ = 0;
  257. translateY = optOffset;
  258. if (Math.abs(translateY) > 170) {
  259. visible = false;
  260. }
  261. }
  262. selected = selectedIndex === i;
  263. if (visible) {
  264. transform += "translate3d(0px," + translateY + "px," + translateZ + "px) ";
  265. if (this.scaleFactor !== 1 && !selected) {
  266. transform += scaleStr;
  267. }
  268. }
  269. else {
  270. transform = 'translate3d(-9999px,0px,0px)';
  271. }
  272. // Update transition duration
  273. if (duration !== opt._dur) {
  274. opt._dur = duration;
  275. button.style[this._plt.Css.transitionDuration] = durationStr;
  276. }
  277. // Update transform
  278. if (transform !== opt._trans) {
  279. opt._trans = transform;
  280. button.style[this._plt.Css.transform] = transform;
  281. }
  282. // Update selected item
  283. if (selected !== opt._selected) {
  284. opt._selected = selected;
  285. if (selected) {
  286. button.classList.add(PICKER_OPT_SELECTED);
  287. }
  288. else {
  289. button.classList.remove(PICKER_OPT_SELECTED);
  290. }
  291. }
  292. }
  293. this.col.prevSelected = selectedIndex;
  294. if (saveY) {
  295. this.y = y;
  296. }
  297. if (emitChange) {
  298. if (this.lastIndex === undefined) {
  299. // have not set a last index yet
  300. this.lastIndex = this.col.selectedIndex;
  301. }
  302. else if (this.lastIndex !== this.col.selectedIndex) {
  303. // new selected index has changed from the last index
  304. // update the lastIndex and emit that it has changed
  305. this.lastIndex = this.col.selectedIndex;
  306. var ionChange = this.ionChange;
  307. if (ionChange.observers.length > 0) {
  308. this._zone.run(ionChange.emit.bind(ionChange, this.col.options[this.col.selectedIndex]));
  309. }
  310. }
  311. }
  312. };
  313. PickerColumnCmp.prototype.refresh = function () {
  314. var min = this.col.options.length - 1;
  315. var max = 0;
  316. var options = this.col.options;
  317. for (var i = 0; i < options.length; i++) {
  318. if (!options[i].disabled) {
  319. min = Math.min(min, i);
  320. max = Math.max(max, i);
  321. }
  322. }
  323. var selectedIndex = clamp(min, this.col.selectedIndex, max);
  324. if (this.col.prevSelected !== selectedIndex) {
  325. var y = (selectedIndex * this.optHeight) * -1;
  326. this._plt.cancelRaf(this.rafId);
  327. this.velocity = 0;
  328. this.update(y, 150, true, false);
  329. }
  330. };
  331. PickerColumnCmp.decorators = [
  332. { type: Component, args: [{
  333. selector: '.picker-col',
  334. template: '<div *ngIf="col.prefix" class="picker-prefix" [style.width]="col.prefixWidth">{{col.prefix}}</div>' +
  335. '<div class="picker-opts" #colEle [style.max-width]="col.optionsWidth">' +
  336. '<button *ngFor="let o of col.options; let i=index"' +
  337. '[class.picker-opt-disabled]="o.disabled" ' +
  338. 'class="picker-opt" disable-activated (click)="optClick($event, i)">' +
  339. '{{o.text}}' +
  340. '</button>' +
  341. '</div>' +
  342. '<div *ngIf="col.suffix" class="picker-suffix" [style.width]="col.suffixWidth">{{col.suffix}}</div>',
  343. host: {
  344. '[style.max-width]': 'col.columnWidth',
  345. '[class.picker-opts-left]': 'col.align=="left"',
  346. '[class.picker-opts-right]': 'col.align=="right"',
  347. }
  348. },] },
  349. ];
  350. /** @nocollapse */
  351. PickerColumnCmp.ctorParameters = function () { return [
  352. { type: Config, },
  353. { type: Platform, },
  354. { type: ElementRef, },
  355. { type: NgZone, },
  356. { type: Haptic, },
  357. { type: Platform, },
  358. { type: DomController, },
  359. ]; };
  360. PickerColumnCmp.propDecorators = {
  361. 'colEle': [{ type: ViewChild, args: ['colEle',] },],
  362. 'col': [{ type: Input },],
  363. 'ionChange': [{ type: Output },],
  364. };
  365. return PickerColumnCmp;
  366. }());
  367. export { PickerColumnCmp };
  368. //# sourceMappingURL=picker-column.js.map