PickerColumn.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { deepClone } from '../utils/deep-clone';
  2. import { use, isObj, range } from '../utils';
  3. import { preventDefault } from '../utils/event';
  4. const DEFAULT_DURATION = 200;
  5. const [sfc, bem] = use('picker-column');
  6. export default sfc({
  7. props: {
  8. valueKey: String,
  9. className: String,
  10. itemHeight: Number,
  11. defaultIndex: Number,
  12. initialOptions: Array,
  13. visibleItemCount: Number
  14. },
  15. data() {
  16. return {
  17. startY: 0,
  18. offset: 0,
  19. duration: 0,
  20. startOffset: 0,
  21. options: deepClone(this.initialOptions),
  22. currentIndex: this.defaultIndex
  23. };
  24. },
  25. created() {
  26. this.$parent.children && this.$parent.children.push(this);
  27. this.setIndex(this.currentIndex);
  28. },
  29. destroyed() {
  30. const { children } = this.$parent;
  31. children && children.splice(children.indexOf(this), 1);
  32. },
  33. watch: {
  34. defaultIndex() {
  35. this.setIndex(this.defaultIndex);
  36. }
  37. },
  38. computed: {
  39. count() {
  40. return this.options.length;
  41. }
  42. },
  43. methods: {
  44. onTouchStart(event) {
  45. this.startY = event.touches[0].clientY;
  46. this.startOffset = this.offset;
  47. this.duration = 0;
  48. },
  49. onTouchMove(event) {
  50. preventDefault(event);
  51. const deltaY = event.touches[0].clientY - this.startY;
  52. this.offset = range(
  53. this.startOffset + deltaY,
  54. -(this.count * this.itemHeight),
  55. this.itemHeight
  56. );
  57. },
  58. onTouchEnd() {
  59. if (this.offset !== this.startOffset) {
  60. this.duration = DEFAULT_DURATION;
  61. const index = range(Math.round(-this.offset / this.itemHeight), 0, this.count - 1);
  62. this.setIndex(index, true);
  63. }
  64. },
  65. adjustIndex(index) {
  66. index = range(index, 0, this.count);
  67. for (let i = index; i < this.count; i++) {
  68. if (!this.isDisabled(this.options[i])) return i;
  69. }
  70. for (let i = index - 1; i >= 0; i--) {
  71. if (!this.isDisabled(this.options[i])) return i;
  72. }
  73. },
  74. isDisabled(option) {
  75. return isObj(option) && option.disabled;
  76. },
  77. getOptionText(option) {
  78. return isObj(option) && this.valueKey in option ? option[this.valueKey] : option;
  79. },
  80. setIndex(index, userAction) {
  81. index = this.adjustIndex(index) || 0;
  82. this.offset = -index * this.itemHeight;
  83. if (index !== this.currentIndex) {
  84. this.currentIndex = index;
  85. userAction && this.$emit('change', index);
  86. }
  87. },
  88. setValue(value) {
  89. const { options } = this;
  90. for (let i = 0; i < options.length; i++) {
  91. if (this.getOptionText(options[i]) === value) {
  92. return this.setIndex(i);
  93. }
  94. }
  95. },
  96. getValue() {
  97. return this.options[this.currentIndex];
  98. }
  99. },
  100. render(h) {
  101. const { itemHeight, visibleItemCount } = this;
  102. const columnStyle = {
  103. height: itemHeight * visibleItemCount + 'px'
  104. };
  105. const baseOffset = (itemHeight * (visibleItemCount - 1)) / 2;
  106. const wrapperStyle = {
  107. transition: `${this.duration}ms`,
  108. transform: `translate3d(0, ${this.offset + baseOffset}px, 0)`,
  109. lineHeight: `${itemHeight}px`
  110. };
  111. const optionStyle = {
  112. height: `${itemHeight}px`
  113. };
  114. return (
  115. <div
  116. style={columnStyle}
  117. class={[bem(), this.className]}
  118. onTouchstart={this.onTouchStart}
  119. onTouchmove={this.onTouchMove}
  120. onTouchend={this.onTouchEnd}
  121. onTouchcancel={this.onTouchEnd}
  122. >
  123. <ul style={wrapperStyle}>
  124. {this.options.map((option, index) => (
  125. <li
  126. style={optionStyle}
  127. class={[
  128. 'van-ellipsis',
  129. bem('item', {
  130. disabled: this.isDisabled(option),
  131. selected: index === this.currentIndex
  132. })
  133. ]}
  134. domPropsInnerHTML={this.getOptionText(option)}
  135. onClick={() => {
  136. this.setIndex(index, true);
  137. }}
  138. />
  139. ))}
  140. </ul>
  141. </div>
  142. );
  143. }
  144. });