uikit
Version:
UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.
447 lines (434 loc) • 15.5 kB
JavaScript
/*! UIkit 3.21.0 | https://www.getuikit.com | (c) 2014 - 2024 YOOtheme | MIT License */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('uikit-util')) :
typeof define === 'function' && define.amd ? define('uikittooltip', ['uikit-util'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.UIkitTooltip = factory(global.UIkit.util));
})(this, (function (util) { 'use strict';
function parseOptions(options, args = []) {
try {
return options ? util.startsWith(options, "{") ? JSON.parse(options) : args.length && !util.includes(options, ":") ? { [args[0]]: options } : options.split(";").reduce((options2, option) => {
const [key, value] = option.split(/:(.*)/);
if (key && !util.isUndefined(value)) {
options2[key.trim()] = value.trim();
}
return options2;
}, {}) : {};
} catch (e) {
return {};
}
}
util.memoize((id, props) => {
const attributes = Object.keys(props);
const filter = attributes.concat(id).map((key) => [util.hyphenate(key), `data-${util.hyphenate(key)}`]).flat();
return { attributes, filter };
});
let id = 1;
function generateId(instance, el = null) {
return (el == null ? void 0 : el.id) || `${instance.$options.id}-${id++}`;
}
var Container = {
props: {
container: Boolean
},
data: {
container: true
},
computed: {
container({ container }) {
return container === true && this.$container || container && util.$(container);
}
}
};
var Position = {
props: {
pos: String,
offset: null,
flip: Boolean,
shift: Boolean,
inset: Boolean
},
data: {
pos: `bottom-${util.isRtl ? "right" : "left"}`,
offset: false,
flip: true,
shift: true,
inset: false
},
connected() {
this.pos = this.$props.pos.split("-").concat("center").slice(0, 2);
[this.dir, this.align] = this.pos;
this.axis = util.includes(["top", "bottom"], this.dir) ? "y" : "x";
},
methods: {
positionAt(element, target, boundary) {
let offset = [this.getPositionOffset(element), this.getShiftOffset(element)];
const placement = [this.flip && "flip", this.shift && "shift"];
const attach = {
element: [this.inset ? this.dir : util.flipPosition(this.dir), this.align],
target: [this.dir, this.align]
};
if (this.axis === "y") {
for (const prop in attach) {
attach[prop].reverse();
}
offset.reverse();
placement.reverse();
}
const restoreScrollPosition = storeScrollPosition(element);
const elDim = util.dimensions(element);
util.css(element, { top: -elDim.height, left: -elDim.width });
util.positionAt(element, target, {
attach,
offset,
boundary,
placement,
viewportOffset: this.getViewportOffset(element)
});
restoreScrollPosition();
},
getPositionOffset(element = this.$el) {
return util.toPx(
this.offset === false ? util.css(element, "--uk-position-offset") : this.offset,
this.axis === "x" ? "width" : "height",
element
) * (util.includes(["left", "top"], this.dir) ? -1 : 1) * (this.inset ? -1 : 1);
},
getShiftOffset(element = this.$el) {
return this.align === "center" ? 0 : util.toPx(
util.css(element, "--uk-position-shift-offset"),
this.axis === "y" ? "width" : "height",
element
) * (util.includes(["left", "top"], this.align) ? 1 : -1);
},
getViewportOffset(element) {
return util.toPx(util.css(element, "--uk-position-viewport-offset"));
}
}
};
function storeScrollPosition(element) {
const scrollElement = util.scrollParent(element);
const { scrollTop } = scrollElement;
return () => {
if (scrollTop !== scrollElement.scrollTop) {
scrollElement.scrollTop = scrollTop;
}
};
}
var Togglable = {
props: {
cls: Boolean,
animation: "list",
duration: Number,
velocity: Number,
origin: String,
transition: String
},
data: {
cls: false,
animation: [false],
duration: 200,
velocity: 0.2,
origin: false,
transition: "ease",
clsEnter: "uk-togglable-enter",
clsLeave: "uk-togglable-leave"
},
computed: {
hasAnimation: ({ animation }) => !!animation[0],
hasTransition: ({ animation }) => ["slide", "reveal"].some((transition) => util.startsWith(animation[0], transition))
},
methods: {
async toggleElement(targets, toggle, animate) {
try {
await Promise.all(
util.toNodes(targets).map((el) => {
const show = util.isBoolean(toggle) ? toggle : !this.isToggled(el);
if (!util.trigger(el, `before${show ? "show" : "hide"}`, [this])) {
return Promise.reject();
}
const promise = (util.isFunction(animate) ? animate : animate === false || !this.hasAnimation ? toggleInstant : this.hasTransition ? toggleTransition : toggleAnimation)(el, show, this);
const cls = show ? this.clsEnter : this.clsLeave;
util.addClass(el, cls);
util.trigger(el, show ? "show" : "hide", [this]);
const done = () => {
util.removeClass(el, cls);
util.trigger(el, show ? "shown" : "hidden", [this]);
};
return promise ? promise.then(done, () => {
util.removeClass(el, cls);
return Promise.reject();
}) : done();
})
);
return true;
} catch (e) {
return false;
}
},
isToggled(el = this.$el) {
el = util.toNode(el);
return util.hasClass(el, this.clsEnter) ? true : util.hasClass(el, this.clsLeave) ? false : this.cls ? util.hasClass(el, this.cls.split(" ")[0]) : util.isVisible(el);
},
_toggle(el, toggled) {
if (!el) {
return;
}
toggled = Boolean(toggled);
let changed;
if (this.cls) {
changed = util.includes(this.cls, " ") || toggled !== util.hasClass(el, this.cls);
changed && util.toggleClass(el, this.cls, util.includes(this.cls, " ") ? void 0 : toggled);
} else {
changed = toggled === el.hidden;
changed && (el.hidden = !toggled);
}
util.$$("[autofocus]", el).some((el2) => util.isVisible(el2) ? el2.focus() || true : el2.blur());
if (changed) {
util.trigger(el, "toggled", [toggled, this]);
}
}
}
};
function toggleInstant(el, show, { _toggle }) {
util.Animation.cancel(el);
util.Transition.cancel(el);
return _toggle(el, show);
}
async function toggleTransition(el, show, { animation, duration, velocity, transition, _toggle }) {
var _a;
const [mode = "reveal", startProp = "top"] = ((_a = animation[0]) == null ? void 0 : _a.split("-")) || [];
const dirs = [
["left", "right"],
["top", "bottom"]
];
const dir = dirs[util.includes(dirs[0], startProp) ? 0 : 1];
const end = dir[1] === startProp;
const props = ["width", "height"];
const dimProp = props[dirs.indexOf(dir)];
const marginProp = `margin-${dir[0]}`;
const marginStartProp = `margin-${startProp}`;
let currentDim = util.dimensions(el)[dimProp];
const inProgress = util.Transition.inProgress(el);
await util.Transition.cancel(el);
if (show) {
_toggle(el, true);
}
const prevProps = Object.fromEntries(
[
"padding",
"border",
"width",
"height",
"minWidth",
"minHeight",
"overflowY",
"overflowX",
marginProp,
marginStartProp
].map((key) => [key, el.style[key]])
);
const dim = util.dimensions(el);
const currentMargin = util.toFloat(util.css(el, marginProp));
const marginStart = util.toFloat(util.css(el, marginStartProp));
const endDim = dim[dimProp] + marginStart;
if (!inProgress && !show) {
currentDim += marginStart;
}
const [wrapper] = util.wrapInner(el, "<div>");
util.css(wrapper, {
boxSizing: "border-box",
height: dim.height,
width: dim.width,
...util.css(el, [
"overflow",
"padding",
"borderTop",
"borderRight",
"borderBottom",
"borderLeft",
"borderImage",
marginStartProp
])
});
util.css(el, {
padding: 0,
border: 0,
minWidth: 0,
minHeight: 0,
[marginStartProp]: 0,
width: dim.width,
height: dim.height,
overflow: "hidden",
[dimProp]: currentDim
});
const percent = currentDim / endDim;
duration = (velocity * endDim + duration) * (show ? 1 - percent : percent);
const endProps = { [dimProp]: show ? endDim : 0 };
if (end) {
util.css(el, marginProp, endDim - currentDim + currentMargin);
endProps[marginProp] = show ? currentMargin : endDim + currentMargin;
}
if (!end ^ mode === "reveal") {
util.css(wrapper, marginProp, -endDim + currentDim);
util.Transition.start(wrapper, { [marginProp]: show ? 0 : -endDim }, duration, transition);
}
try {
await util.Transition.start(el, endProps, duration, transition);
} finally {
util.css(el, prevProps);
util.unwrap(wrapper.firstChild);
if (!show) {
_toggle(el, false);
}
}
}
function toggleAnimation(el, show, cmp) {
const { animation, duration, _toggle } = cmp;
if (show) {
_toggle(el, true);
return util.Animation.in(el, animation[0], duration, cmp.origin);
}
return util.Animation.out(el, animation[1] || animation[0], duration, cmp.origin).then(
() => _toggle(el, false)
);
}
const keyMap = {
TAB: 9,
ESC: 27,
SPACE: 32,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
};
var Component = {
mixins: [Container, Togglable, Position],
data: {
pos: "top",
animation: ["uk-animation-scale-up"],
duration: 100,
cls: "uk-active"
},
connected() {
makeFocusable(this.$el);
},
disconnected() {
this.hide();
},
methods: {
show() {
if (this.isToggled(this.tooltip || null)) {
return;
}
const { delay = 0, title } = parseProps(this.$options);
if (!title) {
return;
}
const titleAttr = util.attr(this.$el, "title");
const off = util.on(this.$el, ["blur", util.pointerLeave], (e) => !util.isTouch(e) && this.hide());
this.reset = () => {
util.attr(this.$el, { title: titleAttr, "aria-describedby": null });
off();
};
const id = generateId(this);
util.attr(this.$el, { title: null, "aria-describedby": id });
clearTimeout(this.showTimer);
this.showTimer = setTimeout(() => this._show(title, id), delay);
},
async hide() {
var _a;
if (util.matches(this.$el, "input:focus")) {
return;
}
clearTimeout(this.showTimer);
if (this.isToggled(this.tooltip || null)) {
await this.toggleElement(this.tooltip, false, false);
}
(_a = this.reset) == null ? void 0 : _a.call(this);
util.remove(this.tooltip);
this.tooltip = null;
},
async _show(title, id) {
this.tooltip = util.append(
this.container,
`<div id="${id}" class="uk-${this.$options.name}" role="tooltip"> <div class="uk-${this.$options.name}-inner">${title}</div> </div>`
);
util.on(this.tooltip, "toggled", (e, toggled) => {
if (!toggled) {
return;
}
const update = () => this.positionAt(this.tooltip, this.$el);
update();
const [dir, align] = getAlignment(this.tooltip, this.$el, this.pos);
this.origin = this.axis === "y" ? `${util.flipPosition(dir)}-${align}` : `${align}-${util.flipPosition(dir)}`;
const handlers = [
util.once(
document,
`keydown ${util.pointerDown}`,
this.hide,
false,
(e2) => e2.type === util.pointerDown && !this.$el.contains(e2.target) || e2.type === "keydown" && e2.keyCode === keyMap.ESC
),
util.on([document, ...util.overflowParents(this.$el)], "scroll", update, {
passive: true
})
];
util.once(this.tooltip, "hide", () => handlers.forEach((handler) => handler()), {
self: true
});
});
if (!await this.toggleElement(this.tooltip, true)) {
this.hide();
}
}
},
events: {
// Clicking a button does not give it focus on all browsers and platforms
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#clicking_and_focus
[`focus ${util.pointerEnter} ${util.pointerDown}`](e) {
if (!util.isTouch(e) || e.type === util.pointerDown) {
this.show();
}
}
}
};
function makeFocusable(el) {
if (!util.isFocusable(el)) {
util.attr(el, "tabindex", "0");
}
}
function getAlignment(el, target, [dir, align]) {
const elOffset = util.offset(el);
const targetOffset = util.offset(target);
const properties = [
["left", "right"],
["top", "bottom"]
];
for (const props2 of properties) {
if (elOffset[props2[0]] >= targetOffset[props2[1]]) {
dir = props2[1];
break;
}
if (elOffset[props2[1]] <= targetOffset[props2[0]]) {
dir = props2[0];
break;
}
}
const props = util.includes(properties[0], dir) ? properties[1] : properties[0];
align = props.find((prop) => elOffset[prop] === targetOffset[prop]) || "center";
return [dir, align];
}
function parseProps(options) {
const { el, id, data } = options;
return ["delay", "title"].reduce((obj, key) => ({ [key]: util.data(el, key), ...obj }), {
...parseOptions(util.data(el, id), ["title"]),
...data
});
}
if (typeof window !== "undefined" && window.UIkit) {
window.UIkit.component("tooltip", Component);
}
return Component;
}));