Permalink
Browse files

feat(dom util): new reflow, getBoundingClientRect, eventOn, eventOff …

…methods (#1052)

* feat(dom): b-modal use dom.reflow method

* [collapse] Use dom.reflow from dom-utls

* [carousel] use dom.reflow method from dom utils

* [dom utils] Add getBoundingClientRect method (#1051)

* [dom utils] add eventPOn and eventOff methods

* [img-lazy] Use new dom utils methods

* [carousel] use new dom util methods

* Update img-lazy.vue

* Update img-lazy.vue

* Update img-lazy.vue

* [scrollspy] use dom utils event methods

* [ttoltip.js] Use dom event methods

* [dropdown mixin] Use new dom util methods

* [button] use dom utils

* [button-toolbar] use dom utils

* [pagination] use dom utils

* Update scrollspy.js

* Update button-toolbar.vue

* [pagination-nav] use DOM utils

* Update pagination.vue

* chore(docs): fix typo

* chore(docs): minor update

* chore(docs): minor update

* chore(docs): minor update to form-checkboxes

* Update carousel.vue

* Update carousel.vue

* Update dom.js

* Update dom.js

* Update img-lazy.vue

* Update button-toolbar.vue

* Update dropdown.js

* Update scrollspy.js
  • Loading branch information...
tmorehouse committed Sep 14, 2017
1 parent 970f7b6 commit 346112d5ac1d0a796ee034904a4d8f29862c1350
@@ -10,7 +10,7 @@ for the default radio input.
<p class="m-0">
<code class="bg-transparent text-danger">&lt;b-form-radio&gt;</code> now generates a single
radio input. Please use <code class="bg-transparent text-danger">&lt;b-form-radio-group&gt;</code>
to gerate a series of radio inputs base on the <code class="bg-transparent text-danger">options</code> prop.
to generate a series of radio inputs based on the <code class="bg-transparent text-danger">options</code> prop.
</p>
</div>
@@ -53,3 +53,5 @@ Modify those values as you need to generate different utilities here.
You can also use `mw-100` (`max-width: 100%;`) and `mh-100` (`max-height: 100%;`) utilities as needed.
## Assitional Resources
Refer to [Bootstrap V4](http://getbootstrap.com/) official documentation site for more information.
@@ -1,7 +1,7 @@
import Popper from 'popper.js';
import { assign } from '../utils/object';
import { from as arrayFrom } from '../utils/array';
import { closest, select, isVisible, isDisabled, addClass, removeClass, hasClass, setAttr, removeAttr, getAttr } from '../utils/dom';
import { closest, select, isVisible, isDisabled, addClass, removeClass, hasClass, setAttr, removeAttr, getAttr, eventOn, eventOff } from '../utils/dom';
import BvEvent from './BvEvent';
const NAME = 'tooltp';
@@ -429,14 +429,14 @@ class ToolTip {
clearTimeout(this.$fadeTimeout);
this.$fadeTimeout = null;
transEvents.forEach(evtName => {
tip.removeEventListener(evtName, fnOnce);
eventOff(tip, evtName, fnOnce);
});
// Call complete callback
complete();
};
if (hasClass(tip, ClassName.FADE)) {
transEvents.forEach(evtName => {
this.$tip.addEventListener(evtName, fnOnce);
eventOn(tip, evtName, fnOnce);
});
// Fallback to setTimeout
this.$fadeTimeout = setTimeout(fnOnce, TRANSITION_DURATION);
@@ -553,21 +553,22 @@ class ToolTip {
listen() {
const triggers = this.$config.trigger.trim().split(/\s+/);
const el = this.$element;
// Using 'this' as the handler will get automagically directed to this.handleEvent
// And maintain our binding to 'this'
triggers.forEach(trigger => {
if (trigger === 'click') {
this.$element.addEventListener('click', this);
eventOn(el, 'click', this);
} else if (trigger === 'focus') {
this.$element.addEventListener('focusin', this);
this.$element.addEventListener('focusout', this);
eventOn(el, 'focusin', this);
eventOn(el, 'focusout', this);
} else if (trigger === 'blur') {
// Used to close $tip when element looses focus
this.$element.addEventListener('focusout', this);
eventOn(el, 'focusout', this);
} else if (trigger === 'hover') {
this.$element.addEventListener('mouseenter', this);
this.$element.addEventListener('mouseleave', this);
eventOn(el, 'mouseenter', this);
eventOn(el, 'mouseleave', this);
}
}, this);
}
@@ -576,7 +577,7 @@ class ToolTip {
const events = ['click','focusin','focusout','mouseenter','mouseleave'];
// Using "this" as the handler will get automagically directed to this.handleEvent
events.forEach(evt => {
this.$element.removeEventListener(evt, this);
eventOff(this.$element, evt, this);
}, this);
}
@@ -587,11 +588,12 @@ class ToolTip {
// disabled, then tip not close until no longer disabled or forcefully closed.
return;
}
if (e.type === 'click') {
const type = e.type;
if (type === 'click') {
this.toggle(e);
} else if (e.type === 'focusin' || e.type === 'mouseenter') {
} else if (type === 'focusin' || type === 'mouseenter') {
this.enter(e);
} else if (e.type === 'focusout' || e.type === 'mouseleave') {
} else if (type === 'focusout' || type === 'mouseleave') {
this.leave(e);
}
}
@@ -643,12 +645,16 @@ class ToolTip {
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
if ('ontouchstart' in document.documentElement) {
arrayFrom(document.body.children).forEach(el => {
el[on ? 'addEventListener' : 'removeEventListener']('mouseover', this.noop);
if (on) {
eventOn(el, 'mouseover', this._noop);
} else {
eventOff(el, 'mouseover', this._noop);
}
});
}
}
noop() {
_noop() {
// Empty noop handler for ontouchstart devices
}
@@ -17,7 +17,9 @@
</template>
<script>
import { from as arrayFrom } from '../utils/array'
import { from as arrayFrom } from '../utils/array';
import { isVisible, selectAll } from '../utils/dom';
const ITEM_SELECTOR = [
'.btn:not(.disabled):not([disabled])',
'.form-control:not(.disabled):not([disabled])',
@@ -26,11 +28,6 @@
'input[type="radio"]:not(.disabled)'
].join(',');
// Determine if an HTML element is visible - Faster than CSS check
function isVisible(el) {
return el && (el.offsetWidth > 0 || el.offsetHeight > 0);
}
export default {
computed: {
classObject() {
@@ -100,7 +97,7 @@
}
},
getItems() {
let items = arrayFrom(this.$el.querySelectorAll(ITEM_SELECTOR));
let items = selectAll(ITEM_SELECTOR, this.$el);
items.forEach(item => {
// Ensure tabfocus is -1 on any new elements
item.tabIndex = -1;
@@ -1,6 +1,7 @@
import { mergeData, pluckProps } from "../utils";
import { arrayIncludes, concat } from "../utils/array";
import { assign, keys } from "../utils/object";
import { addClass, removeClass } from "../utils/dom";
import Link, { propsFactory as linkPropsFactory } from "./link";
const btnProps = {
@@ -42,9 +43,9 @@ export const props = assign(linkProps, btnProps);
function handleFocus(evt) {
if (evt.type === "focusin") {
evt.target.classList.add("focus");
addClass(evt.target, "focus");
} else if (evt.type === "focusout") {
evt.target.classList.remove("focus");
removeClass(evt.target, "focus");
}
}
@@ -78,7 +78,7 @@
<script>
import { from as arrayFrom } from '../utils/array';
import { observeDom } from '../utils';
import { selectAll } from '../utils/dom';
import { selectAll, reflow, addClass, removeClass, setAttr, eventOn, eventOff } from '../utils/dom';
import { idMixin } from '../mixins';
// Slide directional classes
@@ -115,12 +115,6 @@
return null;
}
// Function to trigger a reflow of an element layout
// To help prevent this line from being optimized out
function reflow(el) {
return el.offsetHeight;
}
export default {
mixins: [ idMixin ],
data() {
@@ -268,14 +262,17 @@
this.slides.forEach((slide, idx) => {
const n = idx + 1;
const id = this.safeId(`__BV_indicator_${n}_`);
slide.classList[idx === index ? 'add' : 'remove']('active');
slide.setAttribute('aria-current', idx === index ? 'true' : 'false');
slide.setAttribute('aria-posinset', String(n));
slide.setAttribute('aria-setsize', String(numSlides));
if(idx === index) {
addClass(slide, 'active');
} else {
removeClass(slide, 'active');
}
setAttr(slide, 'aria-current', idx === index ? 'true' : 'false');
setAttr(slide, 'aria-posinset', String(n));
setAttr(slide, 'aria-setsize', String(numSlides));
slide.tabIndex = -1;
if (id) {
slide.setAttribute('aria-controlledby',id);
setAttr(slide, 'aria-controlledby', id);
}
});
@@ -340,8 +337,8 @@
// Trigger a reflow of next slide
reflow(nextSlide);
currentSlide.classList.add(direction.dirClass);
nextSlide.classList.add(direction.dirClass);
addClass(currentSlide, direction.dirClass);
addClass(nextSlide, direction.dirClass);
// Transition End handler
let called = false;
@@ -353,23 +350,23 @@
if (this.transitionEndEvent) {
const events = this.transitionEndEvent.split(/\s+/);
events.forEach(event => {
currentSlide.removeEventListener(event, onceTransEnd);
eventOff(currentSlide, event, onceTransEnd);
});
}
this._animationTimeout = null;
nextSlide.classList.remove(direction.dirClass);
nextSlide.classList.remove(direction.overlayClass);
nextSlide.classList.add('active');
removeClass(nextSlide, direction.dirClass);
removeClass(nextSlide, direction.overlayClass);
addClass(nextSlide, 'active');
currentSlide.classList.remove('active');
currentSlide.classList.remove(direction.dirClass);
currentSlide.classList.remove(direction.overlayClass);
removeClass(currentSlide, 'active');
removeClass(currentSlide, direction.dirClass);
removeClass(currentSlide, direction.overlayClass);
currentSlide.setAttribute('aria-current', 'false');
nextSlide.setAttribute('aria-current', 'true');
currentSlide.setAttribute('aria-hidden', 'true');
nextSlide.setAttribute('aria-hidden', 'false');
setAttr(currentSlide, 'aria-current', 'false');
setAttr(nextSlide, 'aria-current', 'true');
setAttr(currentSlide, 'aria-hidden', 'true');
setAttr(nextSlide, 'aria-hidden', 'false');
currentSlide.tabIndex = -1;
nextSlide.tabIndex = -1;
@@ -391,7 +388,7 @@
if (this.transitionEndEvent) {
const events = this.transitionEndEvent.split(/\s+/);
events.forEach(event => {
currentSlide.addEventListener(event, onceTransEnd);
eventOn(currentSlide, event, onceTransEnd);
});
}
// Fallback to setTimeout
@@ -22,7 +22,7 @@
<script>
import { listenOnRootMixin } from '../mixins';
import { hasClass } from '../utils/dom';
import { hasClass, reflow } from '../utils/dom';
// Events we emit on $root
const EVENT_STATE = 'bv::collapse::state';
@@ -92,7 +92,7 @@
},
onEnter(el) {
el.style.height = 0;
this.reflow(el);
reflow(el);
el.style.height = el.scrollHeight + 'px';
this.transitioning = true;
// This should be moved out so we can add cancellable events
@@ -107,7 +107,7 @@
el.style.height = 'auto';
el.style.display = 'block';
el.style.height = el.getBoundingClientRect().height + 'px';
this.reflow(el);
reflow(el);
this.transitioning = true;
el.style.height = 0;
// This should be moved out so we can add cancellable events
@@ -118,10 +118,6 @@
this.transitioning = false;
this.$emit('hidden');
},
reflow(el) {
/* eslint-disable no-unused-expressions */
el.offsetHeight; // Force repaint
},
emitState() {
this.$emit('input', this.show);
// Let v-b-toggle know the state of this collapse
@@ -17,6 +17,7 @@
<script>
import bImg from './img';
import { isVisible, getBCR, eventOn, eventOff } from '../utils/dom';
const THROTTLE = 100;
@@ -118,39 +119,47 @@
this.setListeners(true);
this.checkView();
},
activated() {
this.setListeners(true);
this.checkView();
},
deactivated() {
this.setListeners(false);
},
beforeDdestroy() {
this.setListeners(flase);
this.setListeners(false);
},
methods: {
setListeners(on) {
clearTimeout(this.scrollTimer);
this.scrollTimout = null;
const root = window;
if (on) {
root.addEventListener('scroll', this.onScroll);
root.addEventListener('resize', this.onScroll);
root.addEventListener('orientationchange', this.onScroll);
eventOn(root, 'scroll', this.onScroll);
eventOn(root, 'resize', this.onScroll);
eventOn(root, 'orientationchange', this.onScroll);
} else {
root.removeEventListener('scroll', this.onScroll);
root.removeEventListener('resize', this.onScroll);
root.removeEventListener('orientationchange', this.onScroll);
clearTimeout(this.scrollTimer);
this.scrollTimout = null;
eventOff(root, 'scroll', this.onScroll);
eventOff(root, 'resize', this.onScroll);
eventOff(root, 'orientationchange', this.onScroll);
}
},
checkView() {
// check bounding box + offset to see if we should show
if (this.$el.offsetParent === null || !(this.$el.offsetWidth > 0 || this.$el.offsetHeight > 0)) {
if (!isVisible(this.$el)) {
// Element is hidden, so skip for now
return;
}
const offset = parseInt(this.offset,10) || 0;
const docElement = document.documentElement;
const view = {
left: 0 - offset,
top: 0 - offset,
bottom: document.documentElement.clientHeight + offset,
right: document.documentElement.clientWidth + offset
l: 0 - offset,
t: 0 - offset,
b: docElement.clientHeight + offset,
r: docElement.clientWidth + offset
};
const box = this.$el.getBoundingClientRect();
if (box.right >= view.left && box.bottom >= view.top && box.left <= view.right && box.top <= view.bottom) {
const box = getBCR(this.$el);
if (box.right >= view.l && box.bottom >= view.t && box.left <= view.r && box.top <= view.b) {
// image is in view (or about to be in view)
this.isShown = true;
this.setListeners(false);
@@ -159,10 +168,10 @@
onScroll() {
if (this.isShown) {
this.setListeners(false);
return;
} else {
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(this.checkView, parseInt(this.throttle, 10) || THROTTLE);
}
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(this.checkView, parseInt(this.throttle, 10) || THROTTLE);
}
}
};
Oops, something went wrong.

0 comments on commit 346112d

Please sign in to comment.