Skip to content

Commit 543b9f5

Browse files
committed
chore(compat): unify access to component instance from directive
Getting component instance from directive is different in Vue 2 and Vue 3. Introduce new util to solve this
1 parent 50afc56 commit 543b9f5

File tree

7 files changed

+53
-31
lines changed

7 files changed

+53
-31
lines changed

src/directives/modal/modal.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getRootActionEventName, eventOn, eventOff } from '../../utils/events'
66
import { isString } from '../../utils/inspect'
77
import { keys } from '../../utils/object'
88
import { getEventRoot } from '../../utils/get-event-root'
9+
import { getInstanceFromDirective } from '../../utils/get-instance-from-directive'
910

1011
// Emitted show event for modal
1112
const ROOT_ACTION_EVENT_NAME_SHOW = getRootActionEventName(NAME_MODAL, EVENT_NAME_SHOW)
@@ -53,7 +54,11 @@ const bind = (el, binding, vnode) => {
5354
type === 'click' ||
5455
(type === 'keydown' && (key === CODE_ENTER || key === CODE_SPACE))
5556
) {
56-
getEventRoot(vnode.context).$emit(ROOT_ACTION_EVENT_NAME_SHOW, target, currentTarget)
57+
getEventRoot(getInstanceFromDirective(vnode, binding)).$emit(
58+
ROOT_ACTION_EVENT_NAME_SHOW,
59+
target,
60+
currentTarget
61+
)
5762
}
5863
}
5964
}

src/directives/popover/popover.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { concat } from '../../utils/array'
55
import { getComponentConfig } from '../../utils/config'
66
import { getScopeId } from '../../utils/get-scope-id'
77
import { identity } from '../../utils/identity'
8+
import { getInstanceFromDirective } from '../../utils/get-instance-from-directive'
89
import {
910
isFunction,
1011
isNumber,
@@ -18,6 +19,7 @@ import { toInteger } from '../../utils/number'
1819
import { keys } from '../../utils/object'
1920
import { createNewChildComponent } from '../../utils/create-new-child-component'
2021
import { BVPopover } from '../../components/popover/helpers/bv-popover'
22+
import { nextTick } from '../../vue'
2123

2224
// Key which we use to store tooltip object on element
2325
const BV_POPOVER = '__BV_Popover__'
@@ -186,7 +188,7 @@ const applyPopover = (el, bindings, vnode) => {
186188
}
187189
const config = parseBindings(bindings, vnode)
188190
if (!el[BV_POPOVER]) {
189-
const parent = vnode.context
191+
const parent = getInstanceFromDirective(vnode, bindings)
190192
el[BV_POPOVER] = createNewChildComponent(parent, BVPopover, {
191193
// Add the parent's scoped style attribute data
192194
_scopeId: getScopeId(parent, undefined)
@@ -263,7 +265,7 @@ export const VBPopover = {
263265
// waits until the containing component and children have finished updating
264266
componentUpdated(el, bindings, vnode) {
265267
// Performed in a `$nextTick()` to prevent endless render/update loops
266-
vnode.context.$nextTick(() => {
268+
nextTick(() => {
267269
applyPopover(el, bindings, vnode)
268270
})
269271
},

src/directives/scrollspy/scrollspy.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { mathRound } from '../../utils/math'
44
import { toInteger } from '../../utils/number'
55
import { keys } from '../../utils/object'
66
import { getEventRoot } from '../../utils/get-event-root'
7+
import { getInstanceFromDirective } from '../../utils/get-instance-from-directive'
78
import { BVScrollspy } from './helpers/bv-scrollspy.class'
89

910
// Key we use to store our instance
@@ -65,9 +66,13 @@ const applyScrollspy = (el, bindings, vnode) => /* istanbul ignore next: not eas
6566
}
6667
const config = parseBindings(bindings)
6768
if (el[BV_SCROLLSPY]) {
68-
el[BV_SCROLLSPY].updateConfig(config, getEventRoot(vnode.context))
69+
el[BV_SCROLLSPY].updateConfig(config, getEventRoot(getInstanceFromDirective(vnode, bindings)))
6970
} else {
70-
el[BV_SCROLLSPY] = new BVScrollspy(el, config, getEventRoot(vnode.context))
71+
el[BV_SCROLLSPY] = new BVScrollspy(
72+
el,
73+
config,
74+
getEventRoot(getInstanceFromDirective(vnode, bindings))
75+
)
7176
}
7277
}
7378

src/directives/toggle/toggle.js

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EVENT_OPTIONS_PASSIVE } from '../../constants/events'
44
import { CODE_ENTER, CODE_SPACE } from '../../constants/key-codes'
55
import { RX_HASH, RX_HASH_ID, RX_SPACE_SPLIT } from '../../constants/regex'
66
import { arrayIncludes, concat } from '../../utils/array'
7+
import { getInstanceFromDirective } from '../../utils/get-instance-from-directive'
78
import {
89
addClass,
910
getAttr,
@@ -106,17 +107,17 @@ const removeClickListener = el => {
106107
el[BV_TOGGLE_CLICK_HANDLER] = null
107108
}
108109

109-
const addClickListener = (el, vnode) => {
110+
const addClickListener = (el, instance) => {
110111
removeClickListener(el)
111-
if (vnode.context) {
112+
if (instance) {
112113
const handler = event => {
113114
if (
114115
!(event.type === 'keydown' && !arrayIncludes(KEYDOWN_KEY_CODES, event.keyCode)) &&
115116
!isDisabled(el)
116117
) {
117118
const targets = el[BV_TOGGLE_TARGETS] || []
118119
targets.forEach(target => {
119-
getEventRoot(vnode.context).$emit(ROOT_ACTION_EVENT_NAME_TOGGLE, target)
120+
getEventRoot(instance).$emit(ROOT_ACTION_EVENT_NAME_TOGGLE, target)
120121
})
121122
}
122123
}
@@ -128,19 +129,19 @@ const addClickListener = (el, vnode) => {
128129
}
129130
}
130131

131-
const removeRootListeners = (el, vnode) => {
132-
if (el[BV_TOGGLE_ROOT_HANDLER] && vnode.context) {
133-
getEventRoot(vnode.context).$off(
132+
const removeRootListeners = (el, instance) => {
133+
if (el[BV_TOGGLE_ROOT_HANDLER] && instance) {
134+
getEventRoot(instance).$off(
134135
[ROOT_EVENT_NAME_STATE, ROOT_EVENT_NAME_SYNC_STATE],
135136
el[BV_TOGGLE_ROOT_HANDLER]
136137
)
137138
}
138139
el[BV_TOGGLE_ROOT_HANDLER] = null
139140
}
140141

141-
const addRootListeners = (el, vnode) => {
142-
removeRootListeners(el, vnode)
143-
if (vnode.context) {
142+
const addRootListeners = (el, instance) => {
143+
removeRootListeners(el, instance)
144+
if (instance) {
144145
const handler = (id, state) => {
145146
// `state` will be `true` if target is expanded
146147
if (arrayIncludes(el[BV_TOGGLE_TARGETS] || [], id)) {
@@ -152,7 +153,7 @@ const addRootListeners = (el, vnode) => {
152153
}
153154
el[BV_TOGGLE_ROOT_HANDLER] = handler
154155
// Listen for toggle state changes (public) and sync (private)
155-
getEventRoot(vnode.context).$on([ROOT_EVENT_NAME_STATE, ROOT_EVENT_NAME_SYNC_STATE], handler)
156+
getEventRoot(instance).$on([ROOT_EVENT_NAME_STATE, ROOT_EVENT_NAME_SYNC_STATE], handler)
156157
}
157158
}
158159

@@ -178,7 +179,7 @@ const resetProp = (el, prop) => {
178179
// Handle directive updates
179180
const handleUpdate = (el, binding, vnode) => {
180181
/* istanbul ignore next: should never happen */
181-
if (!IS_BROWSER || !vnode.context) {
182+
if (!IS_BROWSER || !getInstanceFromDirective(vnode, binding)) {
182183
return
183184
}
184185

@@ -218,7 +219,7 @@ const handleUpdate = (el, binding, vnode) => {
218219
// Wrap in a `requestAF()` to allow any previous
219220
// click handling to occur first
220221
requestAF(() => {
221-
addClickListener(el, vnode)
222+
addClickListener(el, getInstanceFromDirective(vnode, binding))
222223
})
223224

224225
// If targets array has changed, update
@@ -229,7 +230,10 @@ const handleUpdate = (el, binding, vnode) => {
229230
// Request a state update from targets so that we can
230231
// ensure expanded state is correct (in most cases)
231232
targets.forEach(target => {
232-
getEventRoot(vnode.context).$emit(ROOT_ACTION_EVENT_NAME_REQUEST_STATE, target)
233+
getEventRoot(getInstanceFromDirective(vnode, binding)).$emit(
234+
ROOT_ACTION_EVENT_NAME_REQUEST_STATE,
235+
target
236+
)
233237
})
234238
}
235239
}
@@ -244,7 +248,7 @@ export const VBToggle = {
244248
// Assume no targets initially
245249
el[BV_TOGGLE_TARGETS] = []
246250
// Add our root listeners
247-
addRootListeners(el, vnode)
251+
addRootListeners(el, getInstanceFromDirective(vnode, binding))
248252
// Initial update of trigger
249253
handleUpdate(el, binding, vnode)
250254
},
@@ -253,7 +257,7 @@ export const VBToggle = {
253257
unbind(el, binding, vnode) {
254258
removeClickListener(el)
255259
// Remove our $root listener
256-
removeRootListeners(el, vnode)
260+
removeRootListeners(el, getInstanceFromDirective(vnode, binding))
257261
// Reset custom props
258262
resetProp(el, BV_TOGGLE_ROOT_HANDLER)
259263
resetProp(el, BV_TOGGLE_CLICK_HANDLER)

src/directives/tooltip/tooltip.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { NAME_TOOLTIP } from '../../constants/components'
22
import { IS_BROWSER } from '../../constants/env'
33
import { EVENT_NAME_SHOW } from '../../constants/events'
44
import { concat } from '../../utils/array'
5+
import { isVue3, nextTick } from '../../vue'
56
import { getComponentConfig } from '../../utils/config'
67
import { getScopeId } from '../../utils/get-scope-id'
78
import { identity } from '../../utils/identity'
9+
import { getInstanceFromDirective } from '../../utils/get-instance-from-directive'
810
import {
911
isFunction,
1012
isNumber,
@@ -69,7 +71,6 @@ const parseBindings = (bindings, vnode) => /* istanbul ignore next: not easy to
6971
variant: getComponentConfig(NAME_TOOLTIP, 'variant'),
7072
customClass: getComponentConfig(NAME_TOOLTIP, 'customClass')
7173
}
72-
7374
// Process `bindings.value`
7475
if (isString(bindings.value) || isNumber(bindings.value)) {
7576
// Value is tooltip content (HTML optionally supported)
@@ -85,8 +86,8 @@ const parseBindings = (bindings, vnode) => /* istanbul ignore next: not easy to
8586
// If title is not provided, try title attribute
8687
if (isUndefined(config.title)) {
8788
// Try attribute
88-
const data = vnode.data || {}
89-
config.title = data.attrs && !isUndefinedOrNull(data.attrs.title) ? data.attrs.title : undefined
89+
const attrs = isVue3 ? vnode.props : (vnode.data || {}).attrs
90+
config.title = attrs && !isUndefinedOrNull(attrs.title) ? attrs.title : undefined
9091
}
9192

9293
// Normalize delay
@@ -191,7 +192,7 @@ const applyTooltip = (el, bindings, vnode) => {
191192
}
192193
const config = parseBindings(bindings, vnode)
193194
if (!el[BV_TOOLTIP]) {
194-
const parent = vnode.context
195+
const parent = getInstanceFromDirective(vnode, bindings)
195196
el[BV_TOOLTIP] = createNewChildComponent(parent, BVTooltip, {
196197
// Add the parent's scoped style attribute data
197198
_scopeId: getScopeId(parent, undefined)
@@ -259,7 +260,7 @@ export const VBTooltip = {
259260
// waits until the containing component and children have finished updating
260261
componentUpdated(el, bindings, vnode) {
261262
// Performed in a `$nextTick()` to prevent render update loops
262-
vnode.context.$nextTick(() => {
263+
nextTick(() => {
263264
applyTooltip(el, bindings, vnode)
264265
})
265266
},

src/directives/visible/visible.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ import { requestAF } from '../../utils/dom'
3636
import { isFunction } from '../../utils/inspect'
3737
import { looseEqual } from '../../utils/loose-equal'
3838
import { clone, keys } from '../../utils/object'
39+
import { nextTick } from '../../vue'
3940

4041
const OBSERVER_PROP_NAME = '__bv__visibility_observer'
4142

4243
class VisibilityObserver {
43-
constructor(el, options, vnode) {
44+
constructor(el, options) {
4445
this.el = el
4546
this.callback = options.callback
4647
this.margin = options.margin || 0
@@ -49,10 +50,10 @@ class VisibilityObserver {
4950
this.visible = undefined
5051
this.doneOnce = false
5152
// Create the observer instance (if possible)
52-
this.createObserver(vnode)
53+
this.createObserver()
5354
}
5455

55-
createObserver(vnode) {
56+
createObserver() {
5657
// Remove any previous observer
5758
if (this.observer) {
5859
/* istanbul ignore next */
@@ -87,7 +88,7 @@ class VisibilityObserver {
8788

8889
// Start observing in a `$nextTick()` (to allow DOM to complete rendering)
8990
/* istanbul ignore next: IntersectionObserver not supported in JSDOM */
90-
vnode.context.$nextTick(() => {
91+
nextTick(() => {
9192
requestAF(() => {
9293
// Placed in an `if` just in case we were destroyed before
9394
// this `requestAnimationFrame` runs
@@ -127,7 +128,7 @@ const destroy = el => {
127128
delete el[OBSERVER_PROP_NAME]
128129
}
129130

130-
const bind = (el, { value, modifiers }, vnode) => {
131+
const bind = (el, { value, modifiers }) => {
131132
// `value` is the callback function
132133
const options = {
133134
margin: '0px',
@@ -146,7 +147,7 @@ const bind = (el, { value, modifiers }, vnode) => {
146147
// Destroy any previous observer
147148
destroy(el)
148149
// Create new observer
149-
el[OBSERVER_PROP_NAME] = new VisibilityObserver(el, options, vnode)
150+
el[OBSERVER_PROP_NAME] = new VisibilityObserver(el, options)
150151
// Store the current modifiers on the object (cloned)
151152
el[OBSERVER_PROP_NAME]._prevModifiers = clone(modifiers)
152153
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { isVue3 } from '../vue'
2+
3+
export const getInstanceFromDirective = (vnode, bindings) =>
4+
isVue3 ? bindings.instance : vnode.context

0 commit comments

Comments
 (0)