1<script>
2export default {
3  name: 'DynamicScrollerItem',
4
5  inject: [
6    'vscrollData',
7    'vscrollParent',
8    'vscrollResizeObserver',
9  ],
10
11  props: {
12    // eslint-disable-next-line vue/require-prop-types
13    item: {
14      required: true,
15    },
16
17    watchData: {
18      type: Boolean,
19      default: false,
20    },
21
22    /**
23     * Indicates if the view is actively used to display an item.
24     */
25    active: {
26      type: Boolean,
27      required: true,
28    },
29
30    index: {
31      type: Number,
32      default: undefined,
33    },
34
35    sizeDependencies: {
36      type: [Array, Object],
37      default: null,
38    },
39
40    emitResize: {
41      type: Boolean,
42      default: false,
43    },
44
45    tag: {
46      type: String,
47      default: 'div',
48    },
49  },
50
51  computed: {
52    id () {
53      return this.vscrollData.simpleArray ? this.index : this.item[this.vscrollData.keyField]
54    },
55
56    size () {
57      return (this.vscrollData.validSizes[this.id] && this.vscrollData.sizes[this.id]) || 0
58    },
59
60    finalActive () {
61      return this.active && this.vscrollData.active
62    },
63  },
64
65  watch: {
66    watchData: 'updateWatchData',
67
68    id () {
69      if (!this.size) {
70        this.onDataUpdate()
71      }
72    },
73
74    finalActive (value) {
75      if (!this.size) {
76        if (value) {
77          if (!this.vscrollParent.$_undefinedMap[this.id]) {
78            this.vscrollParent.$_undefinedSizes++
79            this.vscrollParent.$_undefinedMap[this.id] = true
80          }
81        } else {
82          if (this.vscrollParent.$_undefinedMap[this.id]) {
83            this.vscrollParent.$_undefinedSizes--
84            this.vscrollParent.$_undefinedMap[this.id] = false
85          }
86        }
87      }
88
89      if (this.vscrollResizeObserver) {
90        if (value) {
91          this.observeSize()
92        } else {
93          this.unobserveSize()
94        }
95      } else if (value && this.$_pendingVScrollUpdate === this.id) {
96        this.updateSize()
97      }
98    },
99  },
100
101  created () {
102    if (this.$isServer) return
103
104    this.$_forceNextVScrollUpdate = null
105    this.updateWatchData()
106
107    if (!this.vscrollResizeObserver) {
108      for (const k in this.sizeDependencies) {
109        this.$watch(() => this.sizeDependencies[k], this.onDataUpdate)
110      }
111
112      this.vscrollParent.$on('vscroll:update', this.onVscrollUpdate)
113      this.vscrollParent.$on('vscroll:update-size', this.onVscrollUpdateSize)
114    }
115  },
116
117  mounted () {
118    if (this.vscrollData.active) {
119      this.updateSize()
120      this.observeSize()
121    }
122  },
123
124  beforeDestroy () {
125    this.vscrollParent.$off('vscroll:update', this.onVscrollUpdate)
126    this.vscrollParent.$off('vscroll:update-size', this.onVscrollUpdateSize)
127    this.unobserveSize()
128  },
129
130  methods: {
131    updateSize () {
132      if (this.finalActive) {
133        if (this.$_pendingSizeUpdate !== this.id) {
134          this.$_pendingSizeUpdate = this.id
135          this.$_forceNextVScrollUpdate = null
136          this.$_pendingVScrollUpdate = null
137          this.computeSize(this.id)
138        }
139      } else {
140        this.$_forceNextVScrollUpdate = this.id
141      }
142    },
143
144    updateWatchData () {
145      if (this.watchData) {
146        this.$_watchData = this.$watch('data', () => {
147          this.onDataUpdate()
148        }, {
149          deep: true,
150        })
151      } else if (this.$_watchData) {
152        this.$_watchData()
153        this.$_watchData = null
154      }
155    },
156
157    onVscrollUpdate ({ force }) {
158      // If not active, sechedule a size update when it becomes active
159      if (!this.finalActive && force) {
160        this.$_pendingVScrollUpdate = this.id
161      }
162
163      if (this.$_forceNextVScrollUpdate === this.id || force || !this.size) {
164        this.updateSize()
165      }
166    },
167
168    onDataUpdate () {
169      this.updateSize()
170    },
171
172    computeSize (id) {
173      this.$nextTick(() => {
174        if (this.id === id) {
175          const width = this.$el.offsetWidth
176          const height = this.$el.offsetHeight
177          this.applySize(width, height)
178        }
179        this.$_pendingSizeUpdate = null
180      })
181    },
182
183    applySize (width, height) {
184      const size = Math.round(this.vscrollParent.direction === 'vertical' ? height : width)
185      if (size && this.size !== size) {
186        if (this.vscrollParent.$_undefinedMap[this.id]) {
187          this.vscrollParent.$_undefinedSizes--
188          this.vscrollParent.$_undefinedMap[this.id] = undefined
189        }
190        this.$set(this.vscrollData.sizes, this.id, size)
191        this.$set(this.vscrollData.validSizes, this.id, true)
192        if (this.emitResize) this.$emit('resize', this.id)
193      }
194    },
195
196    observeSize () {
197      if (!this.vscrollResizeObserver) return
198      this.$_parentNode = this.$el.parentNode;
199      this.vscrollResizeObserver.observe(this.$_parentNode)
200      this.$_parentNode.addEventListener('resize', this.onResize)
201    },
202
203    unobserveSize () {
204      if (!this.vscrollResizeObserver) return
205      this.vscrollResizeObserver.unobserve(this.$_parentNode)
206      this.$_parentNode.removeEventListener('resize', this.onResize)
207    },
208
209    onResize (event) {
210      const { width, height } = event.detail.contentRect
211      this.applySize(width, height)
212    },
213  },
214
215  render (h) {
216    return h(this.tag, this.$slots.default)
217  },
218}
219</script>
220