heyui
Version:
A UI components Library.
326 lines (276 loc) • 10.4 kB
JavaScript
import Popper from 'popper.js';
import utils from '../../utils/utils';
const DEFAULT_OPTIONS = {
container: false,
delay: 0,
html: false,
placement: 'top',
triggerOnce: false,
content: '',
disabled: false,
trigger: 'hover focus',
offset: 0,
equalWidth: false
};
/**
* Create a new Pop.js instance
* @class Pop
* @param {HTMLElement} reference - The reference element used to position the pop
* @param {Object} options
* @param {String} options.placement=bottom
* Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -end),
* left(-start, -end)`
*
* @param {HTMLElement} reference - The DOM node used as reference of the pop (it can be a jQuery element).
* @param {Object} options - Configuration of the pop
* @param {HTMLElement|String|false} options.container=false - Append the pop to a specific element.
* @param {Number|Object} options.delay=0
* Delay showing and hiding the pop (ms) - does not apply to manual trigger type.
* If a number is supplied, delay is applied to both hide/show.
* Object structure is: `{ show: 500, hide: 100 }`
* @param {Boolean} options.html=false - Insert HTML into the pop. If false, the content will inserted with `innerText`.
* @param {String|PlacementFunction} options.placement='top' - One of the allowed placements, or a function returning one of them.
* @param {String} options.template='<div class="pop" role="pop"><div class="pop-arrow"></div><div class="pop-inner"></div></div>'
* Base HTML to used when creating the pop.
* The pop's `content` will be injected into the `.pop-inner` or `.pop__inner`.
* `.pop-arrow` or `.pop__arrow` will become the pop's arrow.
* The outermost wrapper element should have the `.pop` class.
* @param {String|HTMLElement|ContentFunction} options.content='' - Default content value if `content` attribute isn't present.
* @param {String} options.trigger='hover focus'
* How pop is triggered - click | hover | focus | manual.
* You may pass multiple triggers; separate them with a space. `manual` cannot be combined with any other trigger.
* @param {HTMLElement} options.boundariesElement
* The element used as boundaries for the pop. For more information refer to Popper.js'
* [boundariesElement docs](https://popper.js.org/popper-documentation.html)
* @param {Number|String} options.offset=0 - Offset of the pop relative to its reference. For more information refer to Popper.js'
* [offset docs](https://popper.js.org/popper-documentation.html)
* @return {Object} instance - The generated pop instance
*/
class Pop {
constructor(reference, options) {
options = utils.extend({}, DEFAULT_OPTIONS, options);
// reference.jquery && (reference = reference[0]);
this.reference = reference;
// options.template =
this.options = options;
const triggerEvents = typeof options.trigger === 'string' ? options.trigger.split(' ').filter((trigger) => {
return ['click', 'hover', 'focus'].indexOf(trigger) !== -1;
}) : [];
this.isOpen = false;
this.arrowSelector = options.arrowSelector;
this.innerSelector = options.innerSelector;
this.triggerEvents = [];
if (options.content.nodeType === 1) {
options.content.style.display = "none";
}
// this.popNode.style.display = 'none';
// this.popperInstance.update();
// this.hide();
// this.popNode.setAttribute('aria-hidden', 'true');
this.setEventListeners(reference, triggerEvents, options);
}
// show() {
// this.show(this.reference, this.options);
// }
toggle() {
if (this.isOpen) {
return this.hide();
} else {
return this.show();
}
}
// show = () => this.show(this.reference, this.options);
// hide = () => this.hide();
// dispose = () => this.dispose();
// toggle = () => {
// if (this.isOpen) {
// return this.hide();
// } else {
// return this.show();
// }
// }
create(reference, template, content) {
const popGenerator = window.document.createElement('div');
popGenerator.innerHTML = template;
const popNode = popGenerator.childNodes[0];
const allowHtml = this.options.html;
popNode.id = `pop_${Math.random().toString(36).substr(2, 10)}`;
const contentNode = popGenerator.querySelector(this.innerSelector);
if (content.nodeType === 1) {
if (allowHtml) contentNode.appendChild(content);
content.style.display = "block";
} else if (utils.isFunction(content)) {
const contentText = content.call(reference);
if (allowHtml) { contentNode.innerHTML = contentText } else { contentNode.innerText = contentText }
} else {
if (allowHtml) { contentNode.innerHTML = content } else { contentNode.innerText = content }
}
return popNode;
}
initPopNode() {
let reference = this.reference;
let options = this.options
const content = options.content || reference.getAttribute('content');
if (!content) { return this; }
const popNode = this.create(reference, options.template, content, options.html);
popNode.setAttribute('aria-describedby', popNode.id);
const container = this.findContainer(options.container, reference);
this.append(popNode, container);
const popperOptions = {
placement: options.placement,
arrowElement: this.arrowSelector,
};
if (options.boundariesElement) {
popperOptions.boundariesElement = options.boundariesElement;
}
// log(1);
this.popperInstance = new Popper(reference, popNode, popperOptions);
this.popNode = popNode;
// this.popperInstance.update();
this.popNode.setAttribute('aria-hidden', 'true');
}
disabled() {
this.options.disabled = true;
}
enabled() {
this.options.disabled = false;
}
show() {
if (this.isOpen || this.options.disabled) { return this; }
this.isOpen = true;
if (this.options.events && utils.isFunction(this.options.events.show)) {
this.options.events.show.call(null);
}
if (!this.popNode) {
this.initPopNode();
}
if (this.options.equalWidth) {
this.popNode.style.minWidth = `${this.reference.clientWidth}px`;
}
this.popNode.style.display = '';
utils.addClass(this.reference, 'h-pop-ref-show');
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.popNode.setAttribute('aria-hidden', 'false');
}, 0);
this.popperInstance.update();
return this;
}
hide() {
if (!this.isOpen) { return this; }
this.isOpen = false;
this.popNode.setAttribute('aria-hidden', 'true');
utils.removeClass(this.reference, 'h-pop-ref-show');
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.popNode.style.display = 'none';
}, this.options.delay);
return this;
}
dispose() {
if (this.documentHandler) {
document.removeEventListener('click', this.documentHandler);
}
if (this.popNode) {
this.hide();
this.popperInstance.destroy();
this.triggerEvents.forEach(({ func, event }) => {
this.popNode.removeEventListener(event, func);
});
this.triggerEvents = [];
this.popNode.parentNode.removeChild(this.popNode);
this.popNode = null;
}
return this;
}
findContainer(container, reference) {
if (typeof container === 'string') {
container = window.document.querySelector(container);
} else if (container === false) {
container = reference.parentNode;
}
return container;
}
append(popNode, container) {
container.appendChild(popNode);
}
setEventListeners(reference, triggerEvents, options) {
const directtriggerEvents = [];
const oppositetriggerEvents = [];
triggerEvents.forEach((event) => {
switch (event) {
case 'hover':
directtriggerEvents.push('mouseenter');
oppositetriggerEvents.push('mouseleave');
break;
case 'focus':
directtriggerEvents.push('focus');
oppositetriggerEvents.push('blur');
break;
case 'click':
directtriggerEvents.push('click');
if (!this.options.triggerOnce) oppositetriggerEvents.push('click');
break;
default:
break;
}
});
directtriggerEvents.forEach((event) => {
const func = (evt) => {
if (this.isOpen === true) { return; }
evt.usedByPop = true;
this.scheduleShow(reference, options, evt);
};
this.triggerEvents.push({ event, func });
reference.addEventListener(event, func);
});
oppositetriggerEvents.forEach((event) => {
const func = (evt) => {
if (evt.usedByPop === true) { return; }
this.scheduleHide(reference, options, evt);
};
this.triggerEvents.push({ event, func });
reference.addEventListener(event, func);
});
if (options.triggerOnBody) {
this.documentHandler = (e) => {
if (!this.popNode || e.target.parentNode == null) return;
if (reference.contains(e.target) || this.popNode.contains(e.target)) {
return false;
}
this.hide();
};
document.addEventListener('click', this.documentHandler);
}
}
scheduleShow() {
// const computedDelay = (delay && delay.show) || delay || 0;
this.show();
}
scheduleHide(reference, options, evt) {
// const computedDelay = (delay && delay.hide) || delay || 0;
if (this.isOpen === false) { return; }
if (!document.body.contains(this.popNode)) { return; }
if (evt.type === 'mouseleave') {
const isSet = this.setPopNodeEvent(evt, reference, options);
if (isSet) { return; }
}
this.hide(reference, options);
}
setPopNodeEvent(evt, reference, options) {
const relatedreference = evt.relatedreference || evt.toElement;
const callback = (evt2) => {
const relatedreference2 = evt2.relatedreference || evt2.toElement;
this.popNode.removeEventListener(evt.type, callback);
if (!reference.contains(relatedreference2)) {
this.scheduleHide(reference, options, evt2);
}
};
if (this.popNode.contains(relatedreference)) {
this.popNode.addEventListener(evt.type, callback);
return true;
}
return false;
}
}
export default Pop;