@gitlab/ui 134.1.1 → 134.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/base/form/form_textarea/form_textarea.js +4 -10
- package/dist/components/base/form/form_textarea/visible.js +50 -0
- package/dist/utils/use_mock_intersection_observer.js +12 -1
- package/package.json +1 -1
- package/src/components/base/form/form_textarea/form_textarea.vue +4 -10
- package/src/components/base/form/form_textarea/visible.js +52 -0
- package/src/utils/use_mock_intersection_observer.js +6 -1
- package/dist/vendor/bootstrap-vue/src/directives/visible/index.js +0 -1
- package/dist/vendor/bootstrap-vue/src/directives/visible/visible.js +0 -183
- package/src/vendor/bootstrap-vue/src/directives/visible/index.js +0 -3
- package/src/vendor/bootstrap-vue/src/directives/visible/visible.js +0 -188
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { toString, uniqueId, isBoolean, toInteger } from 'lodash-es';
|
|
2
2
|
import { toFloat } from '../../../../utils/number_utils';
|
|
3
3
|
import { stopEvent, isVisible } from '../../../../utils/utils';
|
|
4
|
-
import { VBVisible } from '../../../../vendor/bootstrap-vue/src/directives/visible/visible';
|
|
5
4
|
import GlFormCharacterCount from '../form_character_count/form_character_count';
|
|
5
|
+
import { GlVisibleDirective } from './visible';
|
|
6
6
|
import __vue_normalize__ from 'vue-runtime-helpers/dist/normalize-component.js';
|
|
7
7
|
|
|
8
8
|
var script = {
|
|
@@ -11,7 +11,7 @@ var script = {
|
|
|
11
11
|
GlFormCharacterCount
|
|
12
12
|
},
|
|
13
13
|
directives: {
|
|
14
|
-
|
|
14
|
+
GlVisible: GlVisibleDirective
|
|
15
15
|
},
|
|
16
16
|
inheritAttrs: false,
|
|
17
17
|
model: {
|
|
@@ -311,7 +311,6 @@ var script = {
|
|
|
311
311
|
},
|
|
312
312
|
mounted() {
|
|
313
313
|
this.handleAutofocus();
|
|
314
|
-
this.setHeight();
|
|
315
314
|
this.$nextTick(() => {
|
|
316
315
|
this.localId = uniqueId('gl-form-textarea-');
|
|
317
316
|
});
|
|
@@ -443,11 +442,6 @@ var script = {
|
|
|
443
442
|
this.$emit('submit');
|
|
444
443
|
}
|
|
445
444
|
},
|
|
446
|
-
visibleCallback(visible) {
|
|
447
|
-
if (visible) {
|
|
448
|
-
this.$nextTick(this.setHeight);
|
|
449
|
-
}
|
|
450
|
-
},
|
|
451
445
|
setHeight() {
|
|
452
446
|
this.$nextTick(() => {
|
|
453
447
|
window.requestAnimationFrame(() => {
|
|
@@ -503,11 +497,11 @@ var script = {
|
|
|
503
497
|
const __vue_script__ = script;
|
|
504
498
|
|
|
505
499
|
/* template */
|
|
506
|
-
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.showCharacterCount)?_c('div',[_c('textarea',_vm._g(_vm._b({directives:[{name:"
|
|
500
|
+
var __vue_render__ = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return (_vm.showCharacterCount)?_c('div',[_c('textarea',_vm._g(_vm._b({directives:[{name:"gl-visible",rawName:"v-gl-visible",value:(_vm.setHeight),expression:"setHeight"}],ref:"input",class:_vm.computedClass,style:(_vm.computedStyle),attrs:{"aria-describedby":_vm.characterCountTextId},domProps:{"value":_vm.localValue},on:{"keyup":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }return _vm.handleKeyPress.apply(null, arguments)}}},'textarea',_vm.computedAttrs,false),_vm.computedListeners)),_vm._v(" "),_c('gl-form-character-count',{attrs:{"value":_vm.value,"limit":_vm.characterCountLimit,"count-text-id":_vm.characterCountTextId},scopedSlots:_vm._u([{key:"over-limit-text",fn:function(ref){
|
|
507
501
|
var count = ref.count;
|
|
508
502
|
return [_vm._t("character-count-over-limit-text",null,{"count":count})]}},{key:"remaining-count-text",fn:function(ref){
|
|
509
503
|
var count = ref.count;
|
|
510
|
-
return [_vm._t("remaining-character-count-text",null,{"count":count})]}}],null,true)})],1):_c('textarea',_vm._g(_vm._b({directives:[{name:"
|
|
504
|
+
return [_vm._t("remaining-character-count-text",null,{"count":count})]}}],null,true)})],1):_c('textarea',_vm._g(_vm._b({directives:[{name:"gl-visible",rawName:"v-gl-visible",value:(_vm.setHeight),expression:"setHeight"}],ref:"input",class:_vm.computedClass,style:(_vm.computedStyle),domProps:{"value":_vm.localValue},on:{"keyup":function($event){if(!$event.type.indexOf('key')&&_vm._k($event.keyCode,"enter",13,$event.key,"Enter")){ return null; }return _vm.handleKeyPress.apply(null, arguments)}}},'textarea',_vm.computedAttrs,false),_vm.computedListeners))};
|
|
511
505
|
var __vue_staticRenderFns__ = [];
|
|
512
506
|
|
|
513
507
|
/* style */
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isFunction } from 'lodash-es';
|
|
2
|
+
|
|
3
|
+
// IntersectionObserver-backed visibility directive.
|
|
4
|
+
// Calls the function when the element enters the viewport.
|
|
5
|
+
// The element is expanded by ROOT_MARGIN.
|
|
6
|
+
const ROOT_MARGIN = '640px';
|
|
7
|
+
let observer = null;
|
|
8
|
+
|
|
9
|
+
// Exported for testing purposes only
|
|
10
|
+
const resetObserver = () => {
|
|
11
|
+
var _observer;
|
|
12
|
+
(_observer = observer) === null || _observer === void 0 ? void 0 : _observer.disconnect();
|
|
13
|
+
observer = null;
|
|
14
|
+
};
|
|
15
|
+
const attachObserver = (el, callback) => {
|
|
16
|
+
if (!isFunction(callback)) {
|
|
17
|
+
throw TypeError('directive value must be a function');
|
|
18
|
+
}
|
|
19
|
+
if (!observer) {
|
|
20
|
+
// the observer instance is shared for performance reasons
|
|
21
|
+
// more information: https://github.com/WICG/ResizeObserver/issues/59
|
|
22
|
+
observer = new IntersectionObserver(entries => {
|
|
23
|
+
entries.forEach(entry => {
|
|
24
|
+
if (entry.isIntersecting) {
|
|
25
|
+
entry.target.glVisibleHandler();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}, {
|
|
29
|
+
rootMargin: ROOT_MARGIN
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
el.glVisibleHandler = callback;
|
|
33
|
+
observer.observe(el);
|
|
34
|
+
};
|
|
35
|
+
const detachObserver = el => {
|
|
36
|
+
if (el.glVisibleHandler) {
|
|
37
|
+
var _observer2;
|
|
38
|
+
delete el.glVisibleHandler;
|
|
39
|
+
(_observer2 = observer) === null || _observer2 === void 0 ? void 0 : _observer2.unobserve(el);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const GlVisibleDirective = {
|
|
43
|
+
bind(el, _ref) {
|
|
44
|
+
let callback = _ref.value;
|
|
45
|
+
attachObserver(el, callback);
|
|
46
|
+
},
|
|
47
|
+
unbind: detachObserver
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export { GlVisibleDirective, resetObserver };
|
|
@@ -147,9 +147,20 @@ const useMockIntersectionObserver = () => {
|
|
|
147
147
|
const getInstances = () => {
|
|
148
148
|
return instances;
|
|
149
149
|
};
|
|
150
|
+
const observersCount = observer => {
|
|
151
|
+
var _observer$$_observers;
|
|
152
|
+
return (observer === null || observer === void 0 ? void 0 : (_observer$$_observers = observer.$_observers) === null || _observer$$_observers === void 0 ? void 0 : _observer$$_observers.length) || 0;
|
|
153
|
+
};
|
|
154
|
+
const observesElement = (observer, element) => (observer === null || observer === void 0 ? void 0 : observer.$_observers.some(_ref8 => {
|
|
155
|
+
let _ref9 = _slicedToArray(_ref8, 1),
|
|
156
|
+
observedElement = _ref9[0];
|
|
157
|
+
return observedElement === element;
|
|
158
|
+
})) || false;
|
|
150
159
|
return {
|
|
151
160
|
getInstances,
|
|
152
|
-
trigger
|
|
161
|
+
trigger,
|
|
162
|
+
observersCount,
|
|
163
|
+
observesElement
|
|
153
164
|
};
|
|
154
165
|
};
|
|
155
166
|
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { uniqueId, isBoolean, toInteger, toString } from 'lodash-es';
|
|
3
3
|
import { toFloat } from '../../../../utils/number_utils';
|
|
4
4
|
import { isVisible, stopEvent } from '../../../../utils/utils';
|
|
5
|
-
import { VBVisible } from '../../../../vendor/bootstrap-vue/src/directives/visible/visible';
|
|
6
5
|
import GlFormCharacterCount from '../form_character_count/form_character_count.vue';
|
|
6
|
+
import { GlVisibleDirective } from './visible';
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
9
|
name: 'GlFormTextarea',
|
|
@@ -11,7 +11,7 @@ export default {
|
|
|
11
11
|
GlFormCharacterCount,
|
|
12
12
|
},
|
|
13
13
|
directives: {
|
|
14
|
-
|
|
14
|
+
GlVisible: GlVisibleDirective,
|
|
15
15
|
},
|
|
16
16
|
inheritAttrs: false,
|
|
17
17
|
model: {
|
|
@@ -317,7 +317,6 @@ export default {
|
|
|
317
317
|
},
|
|
318
318
|
mounted() {
|
|
319
319
|
this.handleAutofocus();
|
|
320
|
-
this.setHeight();
|
|
321
320
|
this.$nextTick(() => {
|
|
322
321
|
this.localId = uniqueId('gl-form-textarea-');
|
|
323
322
|
});
|
|
@@ -444,11 +443,6 @@ export default {
|
|
|
444
443
|
this.$emit('submit');
|
|
445
444
|
}
|
|
446
445
|
},
|
|
447
|
-
visibleCallback(visible) {
|
|
448
|
-
if (visible) {
|
|
449
|
-
this.$nextTick(this.setHeight);
|
|
450
|
-
}
|
|
451
|
-
},
|
|
452
446
|
setHeight() {
|
|
453
447
|
this.$nextTick(() => {
|
|
454
448
|
window.requestAnimationFrame(() => {
|
|
@@ -513,7 +507,7 @@ export default {
|
|
|
513
507
|
<div v-if="showCharacterCount">
|
|
514
508
|
<textarea
|
|
515
509
|
ref="input"
|
|
516
|
-
v-
|
|
510
|
+
v-gl-visible="setHeight"
|
|
517
511
|
:value="localValue"
|
|
518
512
|
:class="computedClass"
|
|
519
513
|
:style="computedStyle"
|
|
@@ -560,7 +554,7 @@ export default {
|
|
|
560
554
|
<textarea
|
|
561
555
|
v-else
|
|
562
556
|
ref="input"
|
|
563
|
-
v-
|
|
557
|
+
v-gl-visible="setHeight"
|
|
564
558
|
:value="localValue"
|
|
565
559
|
:class="computedClass"
|
|
566
560
|
:style="computedStyle"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isFunction } from 'lodash-es';
|
|
2
|
+
|
|
3
|
+
// IntersectionObserver-backed visibility directive.
|
|
4
|
+
// Calls the function when the element enters the viewport.
|
|
5
|
+
// The element is expanded by ROOT_MARGIN.
|
|
6
|
+
const ROOT_MARGIN = '640px';
|
|
7
|
+
|
|
8
|
+
let observer = null;
|
|
9
|
+
|
|
10
|
+
// Exported for testing purposes only
|
|
11
|
+
export const resetObserver = () => {
|
|
12
|
+
observer?.disconnect();
|
|
13
|
+
observer = null;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const attachObserver = (el, callback) => {
|
|
17
|
+
if (!isFunction(callback)) {
|
|
18
|
+
throw TypeError('directive value must be a function');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!observer) {
|
|
22
|
+
// the observer instance is shared for performance reasons
|
|
23
|
+
// more information: https://github.com/WICG/ResizeObserver/issues/59
|
|
24
|
+
observer = new IntersectionObserver(
|
|
25
|
+
(entries) => {
|
|
26
|
+
entries.forEach((entry) => {
|
|
27
|
+
if (entry.isIntersecting) {
|
|
28
|
+
entry.target.glVisibleHandler();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
{ rootMargin: ROOT_MARGIN },
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
el.glVisibleHandler = callback;
|
|
37
|
+
observer.observe(el);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const detachObserver = (el) => {
|
|
41
|
+
if (el.glVisibleHandler) {
|
|
42
|
+
delete el.glVisibleHandler;
|
|
43
|
+
observer?.unobserve(el);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const GlVisibleDirective = {
|
|
48
|
+
bind(el, { value: callback }) {
|
|
49
|
+
attachObserver(el, callback);
|
|
50
|
+
},
|
|
51
|
+
unbind: detachObserver,
|
|
52
|
+
};
|
|
@@ -90,5 +90,10 @@ export const useMockIntersectionObserver = () => {
|
|
|
90
90
|
return instances;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
|
|
93
|
+
const observersCount = (observer) => observer?.$_observers?.length || 0;
|
|
94
|
+
|
|
95
|
+
const observesElement = (observer, element) =>
|
|
96
|
+
observer?.$_observers.some(([observedElement]) => observedElement === element) || false;
|
|
97
|
+
|
|
98
|
+
return { getInstances, trigger, observersCount, observesElement };
|
|
94
99
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { VBVisible } from './visible';
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { RX_DIGITS } from '../../constants/regex';
|
|
2
|
-
import { requestAF } from '../../utils/dom';
|
|
3
|
-
import { isFunction } from '../../utils/inspect';
|
|
4
|
-
import { looseEqual } from '../../utils/loose-equal';
|
|
5
|
-
import { keys, clone } from '../../utils/object';
|
|
6
|
-
import { getInstanceFromDirective } from '../../utils/get-instance-from-directive';
|
|
7
|
-
|
|
8
|
-
// v-b-visible
|
|
9
|
-
// Private visibility check directive
|
|
10
|
-
// Based on IntersectionObserver
|
|
11
|
-
//
|
|
12
|
-
// Usage:
|
|
13
|
-
// v-b-visibility.<margin>.<once>="<callback>"
|
|
14
|
-
//
|
|
15
|
-
// Value:
|
|
16
|
-
// <callback>: method to be called when visibility state changes, receives one arg:
|
|
17
|
-
// true: element is visible
|
|
18
|
-
// false: element is not visible
|
|
19
|
-
// null: IntersectionObserver not supported
|
|
20
|
-
//
|
|
21
|
-
// Modifiers:
|
|
22
|
-
// <margin>: a positive decimal value of pixels away from viewport edge
|
|
23
|
-
// before being considered "visible". default is 0
|
|
24
|
-
// <once>: keyword 'once', meaning when the element becomes visible and
|
|
25
|
-
// callback is called observation/notification will stop.
|
|
26
|
-
//
|
|
27
|
-
// When used in a render function:
|
|
28
|
-
// export default {
|
|
29
|
-
// directives: { 'b-visible': VBVisible },
|
|
30
|
-
// render(h) {
|
|
31
|
-
// h(
|
|
32
|
-
// 'div',
|
|
33
|
-
// {
|
|
34
|
-
// directives: [
|
|
35
|
-
// { name: 'b-visible', value=this.callback, modifiers: { '123':true, 'once':true } }
|
|
36
|
-
// ]
|
|
37
|
-
// }
|
|
38
|
-
// )
|
|
39
|
-
// }
|
|
40
|
-
|
|
41
|
-
const OBSERVER_PROP_NAME = '__bv__visibility_observer';
|
|
42
|
-
class VisibilityObserver {
|
|
43
|
-
constructor(el, options, instance) {
|
|
44
|
-
this.el = el;
|
|
45
|
-
this.callback = options.callback;
|
|
46
|
-
this.margin = options.margin || 0;
|
|
47
|
-
this.once = options.once || false;
|
|
48
|
-
this.observer = null;
|
|
49
|
-
this.visible = undefined;
|
|
50
|
-
this.doneOnce = false;
|
|
51
|
-
this.instance = instance;
|
|
52
|
-
// Create the observer instance (if possible)
|
|
53
|
-
this.createObserver();
|
|
54
|
-
}
|
|
55
|
-
createObserver() {
|
|
56
|
-
// Remove any previous observer
|
|
57
|
-
if (this.observer) {
|
|
58
|
-
/* istanbul ignore next */
|
|
59
|
-
this.stop();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Should only be called once and `callback` prop should be a function
|
|
63
|
-
if (this.doneOnce || !isFunction(this.callback)) {
|
|
64
|
-
/* istanbul ignore next */
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Create the observer instance
|
|
69
|
-
try {
|
|
70
|
-
// Future: Possibly add in other modifiers for left/right/top/bottom
|
|
71
|
-
// offsets, root element reference, and thresholds
|
|
72
|
-
this.observer = new IntersectionObserver(this.handler.bind(this), {
|
|
73
|
-
// `null` = 'viewport'
|
|
74
|
-
root: null,
|
|
75
|
-
// Pixels away from view port to consider "visible"
|
|
76
|
-
rootMargin: this.margin,
|
|
77
|
-
// Intersection ratio of el and root (as a value from 0 to 1)
|
|
78
|
-
threshold: 0
|
|
79
|
-
});
|
|
80
|
-
} catch {
|
|
81
|
-
// No IntersectionObserver support, so just stop trying to observe
|
|
82
|
-
this.doneOnce = true;
|
|
83
|
-
this.observer = undefined;
|
|
84
|
-
this.callback(null);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Start observing in a `$nextTick()` (to allow DOM to complete rendering)
|
|
89
|
-
/* istanbul ignore next: IntersectionObserver not supported in JSDOM */
|
|
90
|
-
this.instance.$nextTick(() => {
|
|
91
|
-
requestAF(() => {
|
|
92
|
-
// Placed in an `if` just in case we were destroyed before
|
|
93
|
-
// this `requestAnimationFrame` runs
|
|
94
|
-
if (this.observer) {
|
|
95
|
-
this.observer.observe(this.el);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* istanbul ignore next */
|
|
102
|
-
handler(entries) {
|
|
103
|
-
const entry = entries ? entries[0] : {};
|
|
104
|
-
const isIntersecting = Boolean(entry.isIntersecting || entry.intersectionRatio > 0.0);
|
|
105
|
-
if (isIntersecting !== this.visible) {
|
|
106
|
-
this.visible = isIntersecting;
|
|
107
|
-
this.callback(isIntersecting);
|
|
108
|
-
if (this.once && this.visible) {
|
|
109
|
-
this.doneOnce = true;
|
|
110
|
-
this.stop();
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
stop() {
|
|
115
|
-
/* istanbul ignore next */
|
|
116
|
-
this.observer && this.observer.disconnect();
|
|
117
|
-
this.observer = null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
const destroy = el => {
|
|
121
|
-
const observer = el[OBSERVER_PROP_NAME];
|
|
122
|
-
if (observer && observer.stop) {
|
|
123
|
-
observer.stop();
|
|
124
|
-
}
|
|
125
|
-
delete el[OBSERVER_PROP_NAME];
|
|
126
|
-
};
|
|
127
|
-
const bind = (el, bindings, vnode) => {
|
|
128
|
-
const value = bindings.value,
|
|
129
|
-
modifiers = bindings.modifiers;
|
|
130
|
-
// `value` is the callback function
|
|
131
|
-
const options = {
|
|
132
|
-
margin: '0px',
|
|
133
|
-
once: false,
|
|
134
|
-
callback: value
|
|
135
|
-
};
|
|
136
|
-
// Parse modifiers
|
|
137
|
-
keys(modifiers).forEach(mod => {
|
|
138
|
-
/* istanbul ignore else: Until <b-img-lazy> is switched to use this directive */
|
|
139
|
-
if (RX_DIGITS.test(mod)) {
|
|
140
|
-
options.margin = `${mod}px`;
|
|
141
|
-
} else if (mod.toLowerCase() === 'once') {
|
|
142
|
-
options.once = true;
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
// Destroy any previous observer
|
|
146
|
-
destroy(el);
|
|
147
|
-
// Create new observer
|
|
148
|
-
const instance = getInstanceFromDirective(vnode, bindings);
|
|
149
|
-
el[OBSERVER_PROP_NAME] = new VisibilityObserver(el, options, instance);
|
|
150
|
-
// Store the current modifiers on the object (cloned)
|
|
151
|
-
el[OBSERVER_PROP_NAME]._prevModifiers = clone(modifiers);
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
// When the directive options may have been updated (or element)
|
|
155
|
-
const componentUpdated = (el, bindings, vnode) => {
|
|
156
|
-
const value = bindings.value,
|
|
157
|
-
oldValue = bindings.oldValue,
|
|
158
|
-
modifiers = bindings.modifiers;
|
|
159
|
-
// Compare value/oldValue and modifiers to see if anything has changed
|
|
160
|
-
// and if so, destroy old observer and create new observer
|
|
161
|
-
/* istanbul ignore next */
|
|
162
|
-
const clonedModifiers = clone(modifiers);
|
|
163
|
-
/* istanbul ignore next */
|
|
164
|
-
if (el && (value !== oldValue || !el[OBSERVER_PROP_NAME] || !looseEqual(clonedModifiers, el[OBSERVER_PROP_NAME]._prevModifiers))) {
|
|
165
|
-
// Re-bind on element
|
|
166
|
-
bind(el, bindings, vnode);
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
// When directive un-binds from element
|
|
171
|
-
const unbind = el => {
|
|
172
|
-
// Remove the observer
|
|
173
|
-
destroy(el);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// Export the directive
|
|
177
|
-
const VBVisible = {
|
|
178
|
-
bind,
|
|
179
|
-
componentUpdated,
|
|
180
|
-
unbind
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
export { VBVisible };
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
// v-b-visible
|
|
2
|
-
// Private visibility check directive
|
|
3
|
-
// Based on IntersectionObserver
|
|
4
|
-
//
|
|
5
|
-
// Usage:
|
|
6
|
-
// v-b-visibility.<margin>.<once>="<callback>"
|
|
7
|
-
//
|
|
8
|
-
// Value:
|
|
9
|
-
// <callback>: method to be called when visibility state changes, receives one arg:
|
|
10
|
-
// true: element is visible
|
|
11
|
-
// false: element is not visible
|
|
12
|
-
// null: IntersectionObserver not supported
|
|
13
|
-
//
|
|
14
|
-
// Modifiers:
|
|
15
|
-
// <margin>: a positive decimal value of pixels away from viewport edge
|
|
16
|
-
// before being considered "visible". default is 0
|
|
17
|
-
// <once>: keyword 'once', meaning when the element becomes visible and
|
|
18
|
-
// callback is called observation/notification will stop.
|
|
19
|
-
//
|
|
20
|
-
// When used in a render function:
|
|
21
|
-
// export default {
|
|
22
|
-
// directives: { 'b-visible': VBVisible },
|
|
23
|
-
// render(h) {
|
|
24
|
-
// h(
|
|
25
|
-
// 'div',
|
|
26
|
-
// {
|
|
27
|
-
// directives: [
|
|
28
|
-
// { name: 'b-visible', value=this.callback, modifiers: { '123':true, 'once':true } }
|
|
29
|
-
// ]
|
|
30
|
-
// }
|
|
31
|
-
// )
|
|
32
|
-
// }
|
|
33
|
-
|
|
34
|
-
import { RX_DIGITS } from '../../constants/regex'
|
|
35
|
-
import { requestAF } from '../../utils/dom'
|
|
36
|
-
import { isFunction } from '../../utils/inspect'
|
|
37
|
-
import { looseEqual } from '../../utils/loose-equal'
|
|
38
|
-
import { clone, keys } from '../../utils/object'
|
|
39
|
-
import { getInstanceFromDirective } from '../../utils/get-instance-from-directive'
|
|
40
|
-
|
|
41
|
-
const OBSERVER_PROP_NAME = '__bv__visibility_observer'
|
|
42
|
-
|
|
43
|
-
class VisibilityObserver {
|
|
44
|
-
constructor(el, options, instance) {
|
|
45
|
-
this.el = el
|
|
46
|
-
this.callback = options.callback
|
|
47
|
-
this.margin = options.margin || 0
|
|
48
|
-
this.once = options.once || false
|
|
49
|
-
this.observer = null
|
|
50
|
-
this.visible = undefined
|
|
51
|
-
this.doneOnce = false
|
|
52
|
-
this.instance = instance
|
|
53
|
-
// Create the observer instance (if possible)
|
|
54
|
-
this.createObserver()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
createObserver() {
|
|
58
|
-
// Remove any previous observer
|
|
59
|
-
if (this.observer) {
|
|
60
|
-
/* istanbul ignore next */
|
|
61
|
-
this.stop()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Should only be called once and `callback` prop should be a function
|
|
65
|
-
if (this.doneOnce || !isFunction(this.callback)) {
|
|
66
|
-
/* istanbul ignore next */
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Create the observer instance
|
|
71
|
-
try {
|
|
72
|
-
// Future: Possibly add in other modifiers for left/right/top/bottom
|
|
73
|
-
// offsets, root element reference, and thresholds
|
|
74
|
-
this.observer = new IntersectionObserver(this.handler.bind(this), {
|
|
75
|
-
// `null` = 'viewport'
|
|
76
|
-
root: null,
|
|
77
|
-
// Pixels away from view port to consider "visible"
|
|
78
|
-
rootMargin: this.margin,
|
|
79
|
-
// Intersection ratio of el and root (as a value from 0 to 1)
|
|
80
|
-
threshold: 0
|
|
81
|
-
})
|
|
82
|
-
} catch {
|
|
83
|
-
// No IntersectionObserver support, so just stop trying to observe
|
|
84
|
-
this.doneOnce = true
|
|
85
|
-
this.observer = undefined
|
|
86
|
-
this.callback(null)
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Start observing in a `$nextTick()` (to allow DOM to complete rendering)
|
|
91
|
-
/* istanbul ignore next: IntersectionObserver not supported in JSDOM */
|
|
92
|
-
this.instance.$nextTick(() => {
|
|
93
|
-
requestAF(() => {
|
|
94
|
-
// Placed in an `if` just in case we were destroyed before
|
|
95
|
-
// this `requestAnimationFrame` runs
|
|
96
|
-
if (this.observer) {
|
|
97
|
-
this.observer.observe(this.el)
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/* istanbul ignore next */
|
|
104
|
-
handler(entries) {
|
|
105
|
-
const entry = entries ? entries[0] : {}
|
|
106
|
-
const isIntersecting = Boolean(entry.isIntersecting || entry.intersectionRatio > 0.0)
|
|
107
|
-
if (isIntersecting !== this.visible) {
|
|
108
|
-
this.visible = isIntersecting
|
|
109
|
-
this.callback(isIntersecting)
|
|
110
|
-
if (this.once && this.visible) {
|
|
111
|
-
this.doneOnce = true
|
|
112
|
-
this.stop()
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
stop() {
|
|
118
|
-
/* istanbul ignore next */
|
|
119
|
-
this.observer && this.observer.disconnect()
|
|
120
|
-
this.observer = null
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const destroy = el => {
|
|
125
|
-
const observer = el[OBSERVER_PROP_NAME]
|
|
126
|
-
if (observer && observer.stop) {
|
|
127
|
-
observer.stop()
|
|
128
|
-
}
|
|
129
|
-
delete el[OBSERVER_PROP_NAME]
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const bind = (el, bindings, vnode) => {
|
|
133
|
-
const { value, modifiers } = bindings
|
|
134
|
-
// `value` is the callback function
|
|
135
|
-
const options = {
|
|
136
|
-
margin: '0px',
|
|
137
|
-
once: false,
|
|
138
|
-
callback: value
|
|
139
|
-
}
|
|
140
|
-
// Parse modifiers
|
|
141
|
-
keys(modifiers).forEach(mod => {
|
|
142
|
-
/* istanbul ignore else: Until <b-img-lazy> is switched to use this directive */
|
|
143
|
-
if (RX_DIGITS.test(mod)) {
|
|
144
|
-
options.margin = `${mod}px`
|
|
145
|
-
} else if (mod.toLowerCase() === 'once') {
|
|
146
|
-
options.once = true
|
|
147
|
-
}
|
|
148
|
-
})
|
|
149
|
-
// Destroy any previous observer
|
|
150
|
-
destroy(el)
|
|
151
|
-
// Create new observer
|
|
152
|
-
const instance = getInstanceFromDirective(vnode, bindings)
|
|
153
|
-
el[OBSERVER_PROP_NAME] = new VisibilityObserver(el, options, instance)
|
|
154
|
-
// Store the current modifiers on the object (cloned)
|
|
155
|
-
el[OBSERVER_PROP_NAME]._prevModifiers = clone(modifiers)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// When the directive options may have been updated (or element)
|
|
159
|
-
const componentUpdated = (el, bindings, vnode) => {
|
|
160
|
-
const { value, oldValue, modifiers } = bindings
|
|
161
|
-
// Compare value/oldValue and modifiers to see if anything has changed
|
|
162
|
-
// and if so, destroy old observer and create new observer
|
|
163
|
-
/* istanbul ignore next */
|
|
164
|
-
const clonedModifiers = clone(modifiers)
|
|
165
|
-
/* istanbul ignore next */
|
|
166
|
-
if (
|
|
167
|
-
el &&
|
|
168
|
-
(value !== oldValue ||
|
|
169
|
-
!el[OBSERVER_PROP_NAME] ||
|
|
170
|
-
!looseEqual(clonedModifiers, el[OBSERVER_PROP_NAME]._prevModifiers))
|
|
171
|
-
) {
|
|
172
|
-
// Re-bind on element
|
|
173
|
-
bind(el, bindings, vnode)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// When directive un-binds from element
|
|
178
|
-
const unbind = el => {
|
|
179
|
-
// Remove the observer
|
|
180
|
-
destroy(el)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Export the directive
|
|
184
|
-
export const VBVisible = {
|
|
185
|
-
bind,
|
|
186
|
-
componentUpdated,
|
|
187
|
-
unbind
|
|
188
|
-
}
|