Skip to content

Commit 5f3ba9e

Browse files
authored
fix(v-b-visible): fix type error in componentUpdated hook + minor docs update/fixes (#4327)
1 parent 3ba1870 commit 5f3ba9e

File tree

7 files changed

+157
-33
lines changed

7 files changed

+157
-33
lines changed

src/components/table/helpers/mixin-items.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import looseEqual from '../../../utils/loose-equal'
22
import { isArray, isFunction, isNull, isString, isUndefined } from '../../../utils/inspect'
3+
import { clone } from '../../../utils/object'
34
import normalizeFields from './normalize-fields'
45

56
export default {
@@ -50,7 +51,7 @@ export default {
5051
const parent = this.$parent
5152
return this.computedFields.reduce((obj, f) => {
5253
// We use object spread here so we don't mutate the original field object
53-
obj[f.key] = { ...f }
54+
obj[f.key] = clone(f)
5455
if (f.formatter) {
5556
// Normalize formatter to a function ref or `undefined`
5657
let formatter = f.formatter

src/components/table/helpers/mixin-provider.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import looseEqual from '../../../utils/loose-equal'
22
import warn from '../../../utils/warn'
33
import { isArray, isFunction, isPromise } from '../../../utils/inspect'
4+
import { clone } from '../../../utils/object'
45
import listenOnRootMixin from '../../../mixins/listen-on-root'
56

67
export default {
@@ -62,7 +63,7 @@ export default {
6263
ctx.perPage = this.perPage
6364
ctx.currentPage = this.currentPage
6465
}
65-
return { ...ctx }
66+
return clone(ctx)
6667
}
6768
},
6869
watch: {

src/components/table/helpers/normalize-fields.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import startCase from '../../../utils/startcase'
22
import { isArray, isFunction, isObject, isString } from '../../../utils/inspect'
3-
import { keys } from '../../../utils/object'
3+
import { clone, keys } from '../../../utils/object'
44
import { IGNORED_FIELD_KEYS } from './constants'
55

66
// Private function to massage field entry into common object format
@@ -13,7 +13,7 @@ const processField = (key, value) => {
1313
// Formatter shortcut
1414
field = { key: key, formatter: value }
1515
} else if (isObject(value)) {
16-
field = { ...value }
16+
field = clone(value)
1717
field.key = field.key || key
1818
} else if (value !== false) {
1919
// Fallback to just key
@@ -35,7 +35,7 @@ const normalizeFields = (origFields, items) => {
3535
fields.push({ key: f, label: startCase(f) })
3636
} else if (isObject(f) && f.key && isString(f.key)) {
3737
// Full object definition. We use assign so that we don't mutate the original
38-
fields.push({ ...f })
38+
fields.push(clone(f))
3939
} else if (isObject(f) && keys(f).length === 1) {
4040
// Shortcut object (i.e. { 'foo_bar': 'This is Foo Bar' }
4141
const key = keys(f)[0]

src/directives/visible/README.md

Lines changed: 126 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,57 @@
11
# Visible
22

3-
> The `v-b-visible` directive allows you to react when an element becomes visible in the viewport.
3+
> `v-b-visible` is a lightweight directive that allows you to react when an element becomes visible
4+
> in the viewport and/or when it moves out of the viewport (or is no longer visible).
45
56
The `v-b-visible` directive was added in version `2.1.0`.
67

78
## Overview
89

910
- `v-b-visible` will call your callback method with a boolean value indicating if the element is
10-
visible (intersecting) with the viewport.
11+
visible (intersecting with the viewport) or not.
1112
- The directive can be placed on almost any element or component.
12-
- Changes in visibility cqn also be detected (such as `display: none`), as long as the element is
13-
within (or partially within) the viewport, or within the optional offset.
14-
- Several BootstrapVue components use `v-b-visible`, such as `<b-img-lazy>`.
13+
- Changes in visibility can also be detected (such as `display: none`), as long as the element is
14+
within (or partially within) the viewport, or within the optional offset. Note: transitioning to a
15+
non-visible state due to `v-if="false"` _cannot_ be detected.
16+
- Internally, BootstrapVue uses this directive in several components, such as `<b-img-lazy>`.
1517
- The `v-b-visible` directive requires browser support of
1618
[`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
1719
For older browsers that do not support `IntersectionObserver`, you will need to use a
1820
[polyfill](/docs/#js).
21+
- If `IntersectionObserver` support is not detected, then `v-b-visible` will assume the element is
22+
_always visible_, and will call the callback once with the argument set to `true`.
1923

2024
## Directive syntax and usage
2125

2226
```html
23-
<div v-b-visible.[mod].[...]="callback">content</div>
27+
<div v-b-visible.[mod1].[mod2]="callback">content</div>
2428
```
2529

2630
Where `callback` is required:
2731

2832
- A function reference that will be called whenever visibility changes. The callback is passed a
2933
single boolean argument. `true` indicates that the element is intersecting (partially or entirely
3034
visible) in the viewport, or `false` if the element is not visible/intersecting with the viewport.
31-
The callback will be called each time the element's visibility changes (except when hte `once`
35+
The callback will be called each time the element's visibility changes (except when the `once`
3236
modifier is used. See below for details)
3337

34-
Where `[mod]` can be (all optional):
38+
Where `[mod1]` or `[mod2]` can be (all optional):
3539

36-
- A positive number representing the offset (margin) in pixels _away_ from the edge of the viewport
37-
to determine when the element is considered in (or just about to be in) the viewport. The value
38-
adds a margin around the view port. The default value is `0`.
39-
- The keyword `once`. When this modifier is present, the callback will be called once (with the
40-
argument of `true` indicating the element is intersecting/visible) when the element is
41-
intersecting with the viewport. Note the callback may be called prior to this with an argument of
42-
`false` signifying the element is not intersecting/visible.
40+
- A positive integer number representing the offset (margin) in pixels _away_ from the edge of the
41+
_viewport_ to determine when the element is considered in (or just about to be in) the viewport.
42+
The value adds a margin around the viewport. The default value is `0`.
43+
- The keyword `once`. When this modifier is present, the callback will be called only once the first
44+
time the element is visible (with the argument of `true` indicating the element is
45+
intersecting/visible). Note the callback _may be_ called prior to this with an argument of `false`
46+
signifying the element is not intersecting/visible.
4347

44-
### Usage examples
48+
The order of the modifiers is not important.
4549

46-
Basic:
50+
### Usage syntax examples
51+
52+
In all use cases, the callback function is required.
53+
54+
#### Basic (no modifiers)
4755

4856
```html
4957
<template>
@@ -64,8 +72,10 @@ export default {
6472
</script>
6573
```
6674

67-
With viewport offset modifier of 350px (if the element is outside of the physical viewport by at
68-
least 350px, then it will be considered "visible"):
75+
#### With viewport offset modifier
76+
77+
In this example, the modifier value represents 350px (if the element is outside of the physical
78+
viewport by at least 350px, then it will be considered "visible"):
6979

7080
```html
7181
<template>
@@ -86,7 +96,7 @@ export default {
8696
</script>
8797
```
8898

89-
With `once` modifier:
99+
#### With the `once` modifier
90100

91101
```html
92102
<template>
@@ -110,7 +120,7 @@ export default {
110120
</script>
111121
```
112122

113-
With `once` and offset modifiers:
123+
#### With both `once` and offset modifiers
114124

115125
```html
116126
<template>
@@ -133,3 +143,98 @@ export default {
133143
}
134144
</script>
135145
```
146+
147+
## Live examples
148+
149+
Here are two live examples showing two common use cases.
150+
151+
### Visibility of scrolled content
152+
153+
Scroll the container to see the reaction when the `<b-badge>` scrolls into view:
154+
155+
```html
156+
<template>
157+
<div>
158+
<div
159+
:class="[isVisible ? 'bg-info' : 'bg-light', 'border', 'p-2', 'text-center']"
160+
style="height: 85px; overflow-y: scroll;"
161+
>
162+
<p>{{ text }}</p>
163+
<b-badge v-b-visible="handleVisibility">Element with v-b-visible directive</b-badge>
164+
<p>{{ text }}</p>
165+
</div>
166+
<p class="mt-2">
167+
Visible: {{ isVisible }}
168+
</p>
169+
</div>
170+
</template>
171+
172+
<script>
173+
export default {
174+
data() {
175+
return {
176+
isVisible: false,
177+
text: `
178+
Quis magna Lorem anim amet ipsum do mollit sit cillum voluptate ex nulla
179+
tempor. Laborum consequat non elit enim exercitation cillum aliqua
180+
consequat id aliqua. Esse ex consectetur mollit voluptate est in duis
181+
laboris ad sit ipsum anim Lorem. Incididunt veniam velit elit elit veniam
182+
Lorem aliqua quis ullamco deserunt sit enim elit aliqua esse irure. Laborum
183+
nisi sit est tempor laborum mollit labore officia laborum excepteur commodo
184+
non commodo dolor excepteur commodo. Ipsum fugiat ex est consectetur ipsum
185+
commodo tempor sunt in proident. Non elixir food exorcism nacho tequila tasty.
186+
`
187+
}
188+
},
189+
methods: {
190+
handleVisibility(isVisible) {
191+
this.isVisible = isVisible
192+
}
193+
}
194+
}
195+
</script>
196+
197+
<!-- v-b-visible-scroll.vue -->
198+
```
199+
200+
### CSS display visibility detection
201+
202+
Click the button to change the `<div>` visibility state:
203+
204+
```html
205+
<template>
206+
<div>
207+
<b-button @click="show = !show" class="mb-2">Toggle display</b-button>
208+
<p>Visible: {{ isVisible }}</p>
209+
<div class="border p-3" style="height: 6em;">
210+
<!-- We use Vue's `v-show` directive to control the CSS `display` of the div -->
211+
<div v-show="show" class="bg-info p-3">
212+
<b-badge v-b-visible="handleVisibility">Element with v-b-visible directive</b-badge>
213+
</div>
214+
</div>
215+
</div>
216+
</template>
217+
218+
<script>
219+
export default {
220+
data() {
221+
return {
222+
show: true,
223+
isVisible: false
224+
}
225+
},
226+
methods: {
227+
handleVisibility(isVisible) {
228+
this.isVisible = isVisible
229+
}
230+
}
231+
}
232+
</script>
233+
234+
<!-- v-b-visible-display.vue -->
235+
```
236+
237+
## See also
238+
239+
For more details on `IntersectionObserver`, refer to the
240+
[MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)

src/directives/visible/visible.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import looseEqual from '../../utils/loose-equal'
3535
import { requestAF } from '../../utils/dom'
3636
import { isFunction } from '../../utils/inspect'
37-
import { keys } from '../../utils/object'
37+
import { clone, keys } from '../../utils/object'
3838

3939
const OBSERVER_PROP_NAME = '__bv__visibility_observer'
4040

@@ -149,18 +149,21 @@ const bind = (el, { value, modifiers }, vnode) => {
149149
// Create new observer
150150
el[OBSERVER_PROP_NAME] = new VisibilityObserver(el, options, vnode)
151151
// Store the current modifiers on the object (cloned)
152-
el[OBSERVER_PROP_NAME]._prevModifiers = { ...modifiers }
152+
el[OBSERVER_PROP_NAME]._prevModifiers = clone(modifiers)
153153
}
154154

155155
// When the directive options may have been updated (or element)
156156
const componentUpdated = (el, { value, oldValue, modifiers }, vnode) => {
157157
// Compare value/oldValue and modifiers to see if anything has changed
158158
// and if so, destroy old observer and create new observer
159159
/* istanbul ignore next */
160+
modifiers = clone(modifiers)
161+
/* istanbul ignore next */
160162
if (
161-
value !== oldValue ||
162-
!el[OBSERVER_PROP_NAME] ||
163-
!looseEqual(modifiers, el[OBSERVER_PROP_NAME]._prevModifiers)
163+
el &&
164+
(value !== oldValue ||
165+
!el[OBSERVER_PROP_NAME] ||
166+
!looseEqual(modifiers, el[OBSERVER_PROP_NAME]._prevModifiers))
164167
) {
165168
// Re-bind on element
166169
bind(el, { value, modifiers }, vnode)

src/utils/copy-props.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import identity from './identity'
22
import { isArray, isObject } from './inspect'
3+
import { clone } from './object'
34

45
/**
56
* Copies props from one array/object to a new array/object. Prop values
@@ -22,7 +23,7 @@ const copyProps = (props, transformFn = identity) => {
2223
if (props.hasOwnProperty(prop)) {
2324
// If the prop value is an object, do a shallow clone to prevent
2425
// potential mutations to the original object.
25-
copied[transformFn(prop)] = isObject(props[prop]) ? { ...props[prop] } : props[prop]
26+
copied[transformFn(prop)] = isObject(props[prop]) ? clone(props[prop]) : props[prop]
2627
}
2728
}
2829

src/utils/object.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,25 @@ export const isObject = obj => obj !== null && typeof obj === 'object'
3636
*/
3737
export const isPlainObject = obj => Object.prototype.toString.call(obj) === '[object Object]'
3838

39-
// @link https://gist.github.com/bisubus/2da8af7e801ffd813fab7ac221aa7afc
39+
/**
40+
* Shallow copy an object. If the passed in object
41+
* is null or undefined, returns an empty object
42+
*/
43+
export const clone = obj => ({ ...obj })
44+
45+
/**
46+
* Return a shallow copy of object with
47+
* the specified properties omitted
48+
* @link https://gist.github.com/bisubus/2da8af7e801ffd813fab7ac221aa7afc
49+
*/
4050
export const omit = (obj, props) =>
4151
keys(obj)
4252
.filter(key => props.indexOf(key) === -1)
4353
.reduce((result, key) => ({ ...result, [key]: obj[key] }), {})
4454

55+
/**
56+
* Convenience method to create a read-only descriptor
57+
*/
4558
export const readonlyDescriptor = () => ({ enumerable: true, configurable: false, writable: false })
4659

4760
/**

0 commit comments

Comments
 (0)