@nextcloud/vue
Version:
Nextcloud vue components
259 lines (258 loc) • 7.53 kB
JavaScript
import '../assets/NcPopover-DpakVVxI.css';
import Vue, { defineComponent } from "vue";
import { Dropdown } from "floating-vue";
import { createFocusTrap } from "focus-trap";
import { g as getTrapStack } from "./focusTrap-Cecv_gjR.mjs";
import { n as normalizeComponent } from "./_plugin-vue2_normalizer-DU4iP6Vu.mjs";
const _sfc_main$1 = defineComponent({
name: "NcPopoverTriggerProvider",
provide() {
return {
"NcPopover:trigger:shown": () => this.shown,
"NcPopover:trigger:attrs": () => this.triggerAttrs
};
},
props: {
shown: {
type: Boolean,
required: true
},
popupRole: {
type: String,
default: void 0
}
},
computed: {
triggerAttrs() {
return {
"aria-haspopup": this.popupRole,
"aria-expanded": this.shown.toString()
};
}
},
render() {
return this.$scopedSlots.default?.({
attrs: this.triggerAttrs
});
}
});
const _sfc_render$1 = null;
const _sfc_staticRenderFns$1 = null;
var __component__$1 = /* @__PURE__ */ normalizeComponent(
_sfc_main$1,
_sfc_render$1,
_sfc_staticRenderFns$1,
false,
null,
null
);
const NcPopoverTriggerProvider = __component__$1.exports;
const _sfc_main = {
name: "NcPopover",
components: {
Dropdown,
NcPopoverTriggerProvider
},
inheritAttrs: false,
props: {
/**
* Show or hide the popper
* @see https://floating-vue.starpad.dev/api/#shown
*/
shown: {
type: Boolean,
default: false
},
/**
* Popup role
* @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-haspopup#values
*/
popupRole: {
type: String,
default: void 0,
validator: (value) => ["menu", "listbox", "tree", "grid", "dialog", "true"].includes(value)
},
popoverBaseClass: {
type: String,
default: ""
},
/**
* Enable popover focus trap
*/
focusTrap: {
type: Boolean,
default: true
},
/**
* Set element to return focus to after focus trap deactivation
*
* @type {SetReturnFocus}
*/
setReturnFocus: {
default: void 0,
type: [HTMLElement, SVGElement, String, Boolean, Function]
}
},
emits: [
"after-show",
"after-hide",
/**
* @see https://floating-vue.starpad.dev/api/#update-shown
*/
"update:shown"
],
data() {
return {
internalShown: this.shown
};
},
watch: {
shown(value) {
this.internalShown = value;
},
internalShown(value) {
this.$emit("update:shown", value);
}
},
mounted() {
this.checkTriggerA11y();
},
beforeDestroy() {
this.clearFocusTrap();
this.clearEscapeStopPropagation();
},
methods: {
/**
* Check if the trigger has all required a11y attributes.
* Important to check custom trigger button.
*/
checkTriggerA11y() {
if (window.OC?.debug) {
const triggerContainer = this.getPopoverTriggerContainerElement();
const requiredTriggerButton = triggerContainer.querySelector("[aria-expanded]");
if (!requiredTriggerButton) {
Vue.util.warn("It looks like you are using a custom button as a <NcPopover> or other popover #trigger. If you are not using <NcButton> as a trigger, you need to bind attrs from the #trigger slot props to your custom button. See <NcPopover> docs for an example.");
}
}
},
/**
* Remove incorrect aria-describedby attribute from the trigger.
* @see https://github.com/Akryum/floating-vue/blob/8d4f7125aae0e3ea00ba4093d6d2001ab15058f1/packages/floating-vue/src/components/Popper.ts#L734
*/
removeFloatingVueAriaDescribedBy() {
const triggerContainer = this.getPopoverTriggerContainerElement();
const triggerElements = triggerContainer.querySelectorAll("[data-popper-shown]");
for (const el of triggerElements) {
el.removeAttribute("aria-describedby");
}
},
/**
* @return {HTMLElement|undefined}
*/
getPopoverContentElement() {
return this.$refs.popover?.$refs.popperContent?.$el;
},
/**
* @return {HTMLElement|undefined}
*/
getPopoverTriggerContainerElement() {
return this.$refs.popover.$refs.reference;
},
/**
* Add focus trap for accessibility.
*/
async useFocusTrap() {
await this.$nextTick();
if (!this.focusTrap) {
return;
}
const el = this.getPopoverContentElement();
if (!el) {
return;
}
this.$focusTrap = createFocusTrap(el, {
// Prevents to lose focus using esc key
// Focus will be release when popover be hide
escapeDeactivates: false,
allowOutsideClick: true,
setReturnFocus: this.setReturnFocus,
trapStack: getTrapStack()
});
this.$focusTrap.activate();
},
/**
* Remove focus trap
*
* @param {object} options The configuration options for focusTrap
*/
clearFocusTrap(options = {}) {
try {
this.$focusTrap?.deactivate(options);
this.$focusTrap = null;
} catch (err) {
console.warn(err);
}
},
/**
* Add stopPropagation for Escape.
* It prevents global Escape handling after closing popover.
*
* Manual event handling is used here instead of v-on because there is no direct access to the node.
* Alternative - wrap <template #popover> in a div wrapper.
*/
addEscapeStopPropagation() {
const el = this.getPopoverContentElement();
el?.addEventListener("keydown", this.stopKeydownEscapeHandler);
},
/**
* Remove stop Escape handler
*/
clearEscapeStopPropagation() {
const el = this.getPopoverContentElement();
el?.removeEventListener("keydown", this.stopKeydownEscapeHandler);
},
/**
* @param {KeyboardEvent} event - native keydown event
*/
stopKeydownEscapeHandler(event) {
if (event.type === "keydown" && event.key === "Escape") {
event.stopPropagation();
}
},
async afterShow() {
this.removeFloatingVueAriaDescribedBy();
await this.$nextTick();
await this.useFocusTrap();
this.addEscapeStopPropagation();
this.$emit("after-show");
},
afterHide() {
this.clearFocusTrap();
this.clearEscapeStopPropagation();
this.$emit("after-hide");
}
}
};
var _sfc_render = function render() {
var _vm = this, _c = _vm._self._c;
return _c("Dropdown", _vm._g(_vm._b({ ref: "popover", attrs: { "distance": 10, "arrow-padding": 10, "no-auto-focus": true, "popper-class": _vm.popoverBaseClass, "shown": _vm.internalShown }, on: { "update:shown": function($event) {
_vm.internalShown = $event;
}, "apply-show": _vm.afterShow, "apply-hide": _vm.afterHide }, scopedSlots: _vm._u([{ key: "popper", fn: function(slotProps) {
return [_vm._t("default", null, null, slotProps)];
} }], null, true) }, "Dropdown", _vm.$attrs, false), _vm.$listeners), [_c("NcPopoverTriggerProvider", { attrs: { "shown": _vm.internalShown, "popup-role": _vm.popupRole }, scopedSlots: _vm._u([{ key: "default", fn: function(slotProps) {
return [_vm._t("trigger", null, null, slotProps)];
} }], null, true) })], 1);
};
var _sfc_staticRenderFns = [];
var __component__ = /* @__PURE__ */ normalizeComponent(
_sfc_main,
_sfc_render,
_sfc_staticRenderFns,
false,
null,
null
);
const NcPopover = __component__.exports;
export {
NcPopover as N
};