UNPKG

maz-ui

Version:

A standalone components library for Vue.Js 3 & Nuxt.Js 3

326 lines (325 loc) 14.8 kB
import { defineComponent, mergeModels, useAttrs, useTemplateRef, computed, useModel, nextTick, watch, onMounted, onBeforeUnmount, onUnmounted, createElementBlock, openBlock, Fragment, createCommentVNode, createBlock, normalizeStyle, normalizeClass, unref, createElementVNode, mergeProps, renderSlot, Teleport, createVNode, Transition, withCtx, withDirectives } from "vue"; import { offset, hide, shift, autoPlacement, flip, useFloating, autoUpdate } from "@floating-ui/vue"; import { i as isClient } from "../chunks/isClient.WI4oSt66.js"; import { useInstanceUniqId } from "../composables/useInstanceUniqId.js"; import { d as directive } from "../chunks/vClickOutside.DIOiluy0.js"; import { g as getColor } from "../chunks/types.D0Bp_UhS.js"; import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper.B--vMWp3.js"; import '../assets/MazPopover.DU4IHV-5.css';const _hoisted_1 = ["id", "aria-expanded", "aria-haspopup", "aria-describedby", "aria-labelledby"], _hoisted_2 = ["id", "role", "aria-label", "aria-labelledby", "aria-describedby", "aria-modal", "tabindex", "aria-live"], _sfc_main = /* @__PURE__ */ defineComponent({ name: "MazPopover", inheritAttrs: !1, __name: "MazPopover", props: /* @__PURE__ */ mergeModels({ modelValue: { type: Boolean }, position: { default: "auto" }, preferPosition: {}, fallbackPosition: {}, trigger: { default: "click" }, role: { default: "dialog" }, ariaLabel: {}, announceChanges: { type: Boolean, default: !1 }, disabled: { type: Boolean, default: !1 }, offset: { default: 8 }, delay: { default: 0 }, hoverDelay: { default: 150 }, transition: { default: "scale-pop" }, teleportTo: { default: "body" }, overlayClass: {}, panelClass: {}, panelStyle: {}, closeOnClickOutside: { type: Boolean, default: !0 }, closeOnEscape: { type: Boolean, default: !0 }, persistent: { type: Boolean, default: !1 }, id: {}, ariaLabelledby: {}, ariaDescribedby: {}, color: { default: "background" }, trapFocus: { type: Boolean, default: !0 }, keepOpenOnHover: { type: Boolean, default: !1 }, block: { type: Boolean, default: !1 }, positionReference: {} }, { modelValue: { default: !1 }, modelModifiers: {} }), emits: /* @__PURE__ */ mergeModels(["update:model-value", "open", "close", "after-close-animation", "toggle"], ["update:modelValue"]), setup(__props, { expose: __expose, emit: __emit }) { const emits = __emit, triggerId = useInstanceUniqId({ componentName: "MazPopover", providedId: __props.id }), attrs = useAttrs(), triggerRef = useTemplateRef("trigger"), panelRef = useTemplateRef("panel"), middleware = computed(() => { const middleware2 = [ offset(__props.offset), hide(), shift({ padding: 5 }) ]; return __props.position === "auto" && !__props.preferPosition && !__props.fallbackPosition ? middleware2.push(autoPlacement({ allowedPlacements: ["top", "bottom", "left", "right"] })) : middleware2.push(flip({ fallbackPlacements: __props.fallbackPosition ? [__props.fallbackPosition] : void 0 })), middleware2; }), floatingPosition = computed(() => __props.position === "auto" ? __props.preferPosition : __props.position), transitionName = computed(() => ["scale-pop", "scale-fade"].includes(__props.transition) ? `maz-${__props.transition}` : __props.transition), positionRef = computed(() => { if (!__props.positionReference) return triggerRef.value; if (typeof __props.positionReference == "string") { const withinTrigger = triggerRef.value?.querySelector(__props.positionReference); return withinTrigger || (isClient() ? document.querySelector(__props.positionReference) : null); } return __props.positionReference; }), reference = computed(() => positionRef.value || triggerRef.value), { floatingStyles, placement, update, middlewareData } = useFloating( reference, panelRef, { placement: floatingPosition, middleware, transform: !1, whileElementsMounted: autoUpdate } ), computedPosition = computed(() => placement.value ?? floatingPosition.value), isOpen = useModel(__props, "modelValue"); let openTimeout = null, closeTimeout = null, initialFocusElement = null, ignoreNextClickOutside = !1; const panelId = computed(() => `${triggerId.value}-panel`), rootStyles = computed(() => attrs.style), panelStyles = computed(() => ({ ...floatingStyles.value, pointerEvents: isOpen.value ? "auto" : "none" })), isTouchDevice = computed(() => isClient() ? "ontouchstart" in globalThis || navigator.maxTouchPoints > 0 : !1), effectiveTrigger = computed(() => __props.trigger === "adaptive" ? isTouchDevice.value ? "click" : "hover" : __props.trigger), triggerEvents = computed(() => { if (__props.disabled || effectiveTrigger.value === "manual") return {}; const events = {}; return effectiveTrigger.value === "hover" && (events.onMouseenter = () => { clearCloseTimeout(), open(); }, events.onMouseleave = close), effectiveTrigger.value === "click" && (events.onClick = toggle), events; }), panelEvents = computed(() => effectiveTrigger.value !== "hover" ? {} : { onMouseenter: () => { __props.keepOpenOnHover && clearCloseTimeout(); }, onMouseleave: () => { __props.keepOpenOnHover && close(); } }), panelClasses = computed(() => [ __props.overlayClass, __props.panelClass, `--position-${computedPosition.value}`, `--${getColor(__props.color)}` ]); function cleanup() { clearOpenTimeout(), clearCloseTimeout(), isClient() && document.removeEventListener("keydown", onKeydown); } function open() { __props.disabled || (clearCloseTimeout(), effectiveTrigger.value === "click" && (ignoreNextClickOutside = !0), __props.delay > 0 ? openTimeout = setTimeout(() => { setOpen(!0); }, __props.delay) : setOpen(!0)); } function close() { clearOpenTimeout(), __props.delay > 0 && effectiveTrigger.value === "hover" ? closeTimeout = setTimeout(() => { setOpen(!1); }, __props.delay) : effectiveTrigger.value === "hover" && __props.keepOpenOnHover ? closeTimeout = setTimeout(() => { setOpen(!1); }, __props.hoverDelay) : setOpen(!1); } function toggle() { isOpen.value ? close() : open(); } function setOpen(value) { isOpen.value = value, ignoreNextClickOutside = !1, value ? (emits("open"), emits("toggle", value), nextTick(() => { update(), setupFocusTrap(); })) : (emits("toggle", value), emits("close"), __props.trapFocus && restoreFocus()); } function clearOpenTimeout() { openTimeout && (clearTimeout(openTimeout), openTimeout = null); } function clearCloseTimeout() { closeTimeout && (clearTimeout(closeTimeout), closeTimeout = null); } function getTransformOrigin(position) { switch (position) { case "top": return "center bottom"; case "top-start": return "left bottom"; case "top-end": return "right bottom"; case "bottom": return "center top"; case "bottom-start": return "left top"; case "bottom-end": return "right top"; case "left": return "right center"; case "left-start": return "right top"; case "left-end": return "right bottom"; case "right": return "left center"; case "right-start": return "left top"; case "right-end": return "left bottom"; default: return "center"; } } function setupFocusTrap() { __props.role === "tooltip" || effectiveTrigger.value === "hover" || !__props.trapFocus || !isClient() || (initialFocusElement = document.activeElement, nextTick(() => { const focusableElements = panelRef.value?.querySelectorAll( 'a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select, [tabindex]:not([tabindex="-1"])' ); focusableElements && focusableElements.length > 0 ? focusableElements[0].focus({ preventScroll: !0 }) : panelRef.value?.focus({ preventScroll: !0 }); })); } function restoreFocus() { __props.role === "tooltip" || effectiveTrigger.value === "hover" || !__props.trapFocus || !isClient() || nextTick(() => { initialFocusElement?.focus({ preventScroll: !0 }); }); } function onKeydown(event) { isOpen.value && (event.key === "Escape" && __props.closeOnEscape && !__props.persistent && (event.preventDefault(), close()), event.key === "Tab" && __props.trapFocus && handleTrapFocus(event)); } function handleTrapFocus(event) { if (!panelRef.value || !isClient()) return; const focusableElements = panelRef.value.querySelectorAll( 'a[href], button, textarea, input[type="text"], input[type="radio"], input[type="checkbox"], select, [tabindex]:not([tabindex="-1"])' ); if (focusableElements.length === 0) return; const firstElement = focusableElements[0], lastElement = focusableElements[focusableElements.length - 1]; event.shiftKey ? document.activeElement === firstElement && (event.preventDefault(), lastElement.focus()) : document.activeElement === lastElement && (event.preventDefault(), firstElement.focus()); } function onClickOutside(event) { if (effectiveTrigger.value !== "manual") { if (ignoreNextClickOutside) { ignoreNextClickOutside = !1; return; } if (__props.closeOnClickOutside && !__props.persistent) { if (triggerRef.value && triggerRef.value.contains(event.target)) return; close(); } } } return watch(isOpen, (value, oldValue) => { !isClient() || value === oldValue || (value ? open() : oldValue && !value && close()); }, { immediate: !0 }), watch(() => __props.position, () => { isOpen.value && nextTick(() => update()); }), onMounted(() => { document.addEventListener("keydown", onKeydown); }), onBeforeUnmount(cleanup), onUnmounted(cleanup), __expose({ /** * Open the popover * @description Programmatically open the popover * @usage `mazPopoverInstance.value?.open()` */ open, /** * Close the popover * @description Programmatically close the popover * @usage `mazPopoverInstance.value?.close()` */ close, /** * Toggle the popover * @description Programmatically toggle the popover open/close state * @usage `mazPopoverInstance.value?.toggle()` */ toggle, /** * Check if the popover is open * @type {ModelRef<boolean>} * @description Reactive reference to the popover open state * @usage `const isPopoverOpen = mazPopoverInstance.value?.isOpen` */ isOpen, /** * Update the popover position * @description Manually recalculate and update the popover position * @usage `mazPopoverInstance.value?.updatePosition()` */ updatePosition: update, /** * Panel reference * @type {Ref<HTMLElement>} * @description Reference to the popover panel element * @usage `mazPopoverInstance.value?.panelRef` */ panelRef }), (_ctx, _cache) => (openBlock(), createElementBlock(Fragment, null, [ _ctx.$slots.trigger ? (openBlock(), createElementBlock("div", { key: 0, class: normalizeClass(["m-popover m-reset-css", [ unref(attrs).class, { "--open": isOpen.value, "--disabled": __props.disabled, "--block": __props.block } ]]), style: normalizeStyle(rootStyles.value) }, [ createElementVNode("div", mergeProps({ id: unref(triggerId), ref: "trigger", role: "button", class: "m-popover-trigger", "aria-expanded": __props.role === "dialog" || __props.role === "menu" ? isOpen.value : void 0, "aria-haspopup": __props.role === "dialog" ? "dialog" : void 0, "aria-describedby": __props.role === "tooltip" && isOpen.value ? panelId.value : __props.ariaDescribedby, "aria-labelledby": __props.ariaLabelledby }, triggerEvents.value), [ renderSlot(_ctx.$slots, "trigger", { open, close, toggle, isOpen: isOpen.value, trigger: effectiveTrigger.value }, void 0, !0) ], 16, _hoisted_1) ], 6)) : createCommentVNode("", !0), (openBlock(), createBlock(Teleport, { to: __props.teleportTo }, [ createVNode(Transition, { name: transitionName.value, appear: "", onAfterLeave: _cache[0] || (_cache[0] = ($event) => emits("after-close-animation")) }, { default: withCtx(() => [ isOpen.value ? withDirectives((openBlock(), createElementBlock("div", mergeProps({ key: 0, id: panelId.value, ref: "panel", role: __props.role, "aria-label": __props.ariaLabel, "aria-labelledby": __props.role === "dialog" ? __props.ariaLabelledby || unref(triggerId) : void 0, "aria-describedby": __props.role === "dialog" ? __props.ariaDescribedby : void 0, "aria-modal": __props.role === "dialog" ? "true" : void 0, tabindex: __props.role === "dialog" ? "-1" : void 0, class: ["m-popover-panel", panelClasses.value], "aria-live": __props.announceChanges ? "polite" : void 0, style: [ __props.panelStyle, panelStyles.value, { transformOrigin: getTransformOrigin(computedPosition.value), visibility: unref(middlewareData).hide?.referenceHidden ? "hidden" : "visible" } ] }, panelEvents.value), [ renderSlot(_ctx.$slots, "default", { open, close, toggle, isOpen: isOpen.value }, void 0, !0) ], 16, _hoisted_2)), [ [unref(directive), onClickOutside] ]) : createCommentVNode("", !0) ]), _: 3 }, 8, ["name"]) ], 8, ["to"])) ], 64)); } }), MazPopover = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-641d39cb"]]); export { MazPopover as default };