@dialpad/dialtone
Version:
Dialpad's Dialtone design system monorepo
434 lines (433 loc) • 14.1 kB
JavaScript
"use strict";
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
const tooltip_constants = require("./tooltip_constants.cjs");
const popover_constants = require("../popover/popover_constants.cjs");
const common_utils = require("../../common/utils.cjs");
const tippy_utils = require("../popover/tippy_utils.cjs");
const _pluginVue2_normalizer = require("../../_virtual/_plugin-vue2_normalizer.cjs");
const _sfc_main = {
name: "DtTooltip",
props: {
/**
* The id of the tooltip
*/
id: {
type: String,
default() {
return common_utils.getUniqueString();
}
},
/**
* If the popover does not fit in the direction described by "placement",
* it will attempt to change its direction to the "fallbackPlacements"
* if defined, otherwise it will automatically position to a new location
* as it sees best fit. See
* <a
* class="d-link"
* href="https://popper.js.org/docs/v2/modifiers/flip/#fallbackplacements"
* target="_blank"
* >
* Popper.js docs
* </a>
* */
fallbackPlacements: {
type: Array,
default: () => ["auto"]
},
/**
* If true, applies inverted styles to the tooltip
* @values true, false
*/
inverted: {
type: Boolean,
default: false
},
/**
* Displaces the tooltip from its reference element
* by the specified number of pixels. See
* <a
* class="d-link"
* href="https://atomiks.github.io/tippyjs/v6/all-props/#offset"
* target="_blank"
* >
* Tippy.js docs
* </a>
*/
offset: {
type: Array,
default: () => [0, 12]
},
/**
* The direction the popover displays relative to the anchor. See
* <a
* class="d-link"
* href="https://atomiks.github.io/tippyjs/v6/all-props/#placement"
* target="_blank"
* >
* Tippy.js docs
* </a>
* @values top, top-start, top-end,
* right, right-start, right-end,
* left, left-start, left-end,
* bottom, bottom-start, bottom-end,
* auto, auto-start, auto-end
*/
placement: {
type: String,
default: "top",
validator(placement) {
return tooltip_constants.TOOLTIP_DIRECTIONS.includes(placement);
}
},
/**
* If the tooltip sticks to the anchor. This is usually not needed, but can be needed
* if the reference element's position is animating, or to automatically update the popover
* position in those cases the DOM layout changes the reference element's position.
* `true` enables it, `reference` only checks the "reference" rect for changes and `popper` only
* checks the "popper" rect for changes. See
* <a
* class="d-link"
* href="https://atomiks.github.io/tippyjs/v6/all-props/#sticky"
* target="_blank"
* >
* Tippy.js docs
* </a>
* @values true, false, reference, popper
*/
sticky: {
type: [Boolean, String],
default: true,
validator: (sticky) => {
return tooltip_constants.TOOLTIP_STICKY_VALUES.includes(sticky);
}
},
/**
* Sets the element to which the tooltip is going to append to.
* 'body' will append to the nearest body (supports shadow DOM).
* This prop is not reactive, must be set on initial render.
* @values 'body', 'parent', HTMLElement,
*/
appendTo: {
type: [HTMLElement, String],
default: "body",
validator: (appendTo) => {
return popover_constants.POPOVER_APPEND_TO_VALUES.includes(appendTo) || appendTo instanceof HTMLElement;
}
},
/**
* Additional css classes for the tooltip content element.
* Can accept all of String, Object, and Array, i.e. has the
* same api as Vue's built-in handling of the class attribute.
*/
contentClass: {
type: [String, Object, Array],
default: ""
},
/**
* A provided message for the tooltip content
*/
message: {
type: String,
default: ""
},
/**
* Controls whether hover/focus causes the tooltip to appear.
* Cannot be combined with the show prop. show value will be ignored.
* by default this is true, if you override with false, the tooltip will never show up.
*/
enabled: {
type: Boolean,
default: true
},
/**
* Controls whether the tooltip is shown. Leaving this null will have the tooltip trigger on mouseover by default.
* If you set this value, the default mouseover behavior will be disabled and you can control it as you need.
* Supports .sync modifier
* @values null, true, false
*/
show: {
type: Boolean,
default: null
},
/**
* Whether the tooltip should have a transition effect (fade).
*/
transition: {
type: Boolean,
default: true
},
/**
* Whether the tooltip will have a delay when being focused or moused over.
* @values true, false
*/
delay: {
type: Boolean,
default: true
},
/**
* Set a custom theme on the tooltip. See https://atomiks.github.io/tippyjs/v6/themes/
*/
theme: {
type: String,
default: null
},
/**
* External anchor id to use in those cases the anchor can't be provided via the slot.
* For instance, using the combobox's input as the anchor for the popover.
*/
externalAnchor: {
type: String,
default: null
}
},
emits: [
/**
* Emitted when tooltip is shown or hidden
*
* @event shown
* @type {Boolean}
*/
"shown",
/**
* Sync show value
*
* @event update:show
*/
"update:show"
],
data() {
return {
TOOLTIP_KIND_MODIFIERS: tooltip_constants.TOOLTIP_KIND_MODIFIERS,
tip: null,
inTimer: null,
// Internal state for whether the tooltip is shown. Changing the prop
// will update this.
internalShow: false,
// this is where the placement currently is, this can be different than
// the placement prop when there is not enough available room for the tip
// to display and it uses a fallback placement.
currentPlacement: this.placement
};
},
computed: {
// eslint-disable-next-line complexity
tippyProps() {
return {
offset: this.offset,
delay: this.delay ? tooltip_constants.TOOLTIP_DELAY_MS : false,
placement: this.placement,
sticky: this.sticky,
theme: this.inverted ? "inverted" : this.theme,
animation: this.transition ? "fade" : false,
// onShown only triggers when transition is truthy
onShown: (tooltipInstance) => this.onShow(tooltipInstance, "onShown"),
// onShown will always be called, but it will be called before the animation is complete
onShow: (tooltipInstance) => this.onShow(tooltipInstance, "onShow"),
onHidden: this.onHide,
popperOptions: tippy_utils.getPopperOptions({
fallbackPlacements: this.fallbackPlacements,
hasHideModifierEnabled: true,
onChangePlacement: this.onChangePlacement
})
};
},
anchor() {
return this.externalAnchor ? document.body.querySelector(this.externalAnchor) : tippy_utils.getAnchor(this.$refs.anchor);
}
},
watch: {
tippyProps: {
handler: "setProps",
deep: true
},
show: {
handler: function(show) {
if (show !== null && this.enabled) {
this.internalShow = show;
}
},
immediate: true
},
internalShow(value) {
if (value) {
this.setProps();
this.tip.show();
} else {
this.tip.hide();
}
},
sticky(sticky) {
this.tip.setProps({
sticky
});
}
},
async mounted() {
if (!this.enabled && this.show != null) {
console.warn("Tooltip: You cannot use both the enabled and show props at the same time.");
console.warn("The show prop will be ignored.");
}
this.tip = tippy_utils.createTippy(this.anchor, this.initOptions());
if (this.externalAnchor) {
await common_utils.flushPromises();
this.addExternalAnchorEventListeners();
}
},
beforeDestroy() {
var _a, _b;
this.externalAnchor && this.removeExternalAnchorEventListeners();
if ((_a = this.anchor) == null ? void 0 : _a._tippy) {
(_b = this.tip) == null ? void 0 : _b.destroy();
}
},
methods: {
calculateAnchorZindex() {
if (this.$el.getRootNode().querySelector('.d-modal[aria-hidden="false"], .d-modal--transparent[aria-hidden="false"]') || // Special case because we don't have any dialtone drawer component yet. Render at 651 when
// anchor of popover is within a drawer.
this.$el.closest(".d-zi-drawer")) {
return 651;
} else {
return 400;
}
},
hasVisibleFocus() {
return this.anchor.matches(":focus-visible");
},
onEnterAnchor(e) {
if (!this.enabled) return;
if (this.delay && this.inTimer === null) {
this.inTimer = setTimeout(() => {
this.triggerShow(e);
}, tooltip_constants.TOOLTIP_DELAY_MS);
} else {
this.triggerShow(e);
}
},
triggerShow(e) {
if (e.type === "focusin") {
if (this.show === null && this.hasVisibleFocus()) {
this.internalShow = true;
}
} else {
if (this.show === null) this.internalShow = true;
}
},
onLeaveAnchor(e) {
if (e.type === "keydown" && e.code !== "Escape") return;
clearTimeout(this.inTimer);
this.inTimer = null;
this.triggerHide();
},
triggerHide() {
if (this.show === null) this.internalShow = false;
},
onChangePlacement(placement) {
this.currentPlacement = placement;
},
onHide() {
var _a;
(_a = this.tip) == null ? void 0 : _a.unmount();
this.$emit("shown", false);
if (this.show !== null) {
this.$emit("update:show", false);
}
},
onShow(tooltipInstance, callingMethod) {
if (!this.tooltipHasContent(tooltipInstance)) {
return false;
}
if (this.transition && callingMethod === "onShow") {
return;
}
this.$emit("shown", true);
if (this.show !== null) {
this.$emit("update:show", true);
}
},
setProps() {
var _a, _b;
if (this.tip && this.tip.setProps) {
this.tip.setProps({
...this.tippyProps,
// these need to be set here rather than in tippyProps because they are non-reactive
appendTo: this.appendTo === "body" ? (_b = (_a = this.anchor) == null ? void 0 : _a.getRootNode()) == null ? void 0 : _b.querySelector("body") : this.appendTo,
zIndex: this.calculateAnchorZindex()
});
}
},
onMount() {
this.setProps();
},
tooltipHasContent(tooltipInstance) {
if (tooltipInstance.props.content.textContent.trim().length === 0) {
return false;
}
return true;
},
// set initial options here. If any of the options need to dynamically change, they should be put in
// tippyProps instead.
initOptions() {
const template = this.$refs.content;
return {
content: template,
arrow: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="7"><path d="M 14.5,7 8,0 1.5,7 Z"/></svg>',
// transition duration - same as our custom fade delay in dialtone-globals.less
duration: 180,
interactive: false,
trigger: "manual",
hideOnClick: false,
// disable tooltip from displaying on touch devices
touch: false,
onMount: this.onMount,
showOnCreate: this.internalShow,
popperOptions: tippy_utils.getPopperOptions({
hasHideModifierEnabled: true
})
};
},
addExternalAnchorEventListeners() {
["focusin", "mouseenter"].forEach((listener) => {
var _a;
(_a = this.anchor) == null ? void 0 : _a.addEventListener(listener, (event) => this.onEnterAnchor(event));
});
["focusout", "mouseleave", "keydown"].forEach((listener) => {
var _a;
(_a = this.anchor) == null ? void 0 : _a.addEventListener(listener, (event) => this.onLeaveAnchor(event));
});
},
removeExternalAnchorEventListeners() {
["focusin", "mouseenter"].forEach((listener) => {
var _a;
(_a = this.anchor) == null ? void 0 : _a.removeEventListener(listener, (event) => this.onEnterAnchor(event));
});
["focusout", "mouseleave", "keydown"].forEach((listener) => {
var _a;
(_a = this.anchor) == null ? void 0 : _a.removeEventListener(listener, (event) => this.onLeaveAnchor(event));
});
}
}
};
var _sfc_render = function render() {
var _vm = this, _c = _vm._self._c;
return _c("div", { attrs: { "data-qa": "dt-tooltip-container" } }, [!_vm.externalAnchor ? _c("span", { ref: "anchor", attrs: { "data-qa": "dt-tooltip-anchor" }, on: { "focusin": _vm.onEnterAnchor, "focusout": _vm.onLeaveAnchor, "mouseenter": _vm.onEnterAnchor, "mouseleave": _vm.onLeaveAnchor, "keydown": function($event) {
if (!$event.type.indexOf("key") && _vm._k($event.keyCode, "esc", 27, $event.key, ["Esc", "Escape"])) return null;
return _vm.onLeaveAnchor.apply(null, arguments);
} } }, [_vm._t("anchor")], 2) : _vm._e(), _c("div", _vm._g({ ref: "content", class: [
"d-tooltip",
{
[_vm.TOOLTIP_KIND_MODIFIERS.inverted]: _vm.inverted
},
_vm.contentClass
], attrs: { "id": _vm.id, "data-qa": "dt-tooltip" } }, _vm.$listeners), [_vm._t("default", function() {
return [_vm._v(" " + _vm._s(_vm.message) + " ")];
})], 2)]);
};
var _sfc_staticRenderFns = [];
var __component__ = /* @__PURE__ */ _pluginVue2_normalizer.default(
_sfc_main,
_sfc_render,
_sfc_staticRenderFns
);
const DtTooltip = __component__.exports;
exports.default = DtTooltip;
//# sourceMappingURL=tooltip.vue.cjs.map