Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@featherds/menu

Package Overview
Dependencies
Maintainers
2
Versions
73
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@featherds/menu - npm Package Compare versions

Comparing version 0.9.6 to 0.10.0

198

dist/app.es.js
import { getSafeId } from "@featherds/utils/id";
import { ref, computed, watch, openBlock, createElementBlock, renderSlot, withDirectives, createElementVNode, normalizeClass, vShow, nextTick } from "vue";
import { ref, computed, watch, openBlock, createElementBlock, renderSlot, createBlock, Teleport, withDirectives, createElementVNode, normalizeClass, normalizeStyle, vShow, nextTick } from "vue";
import { useResize } from "@featherds/composables/events/Resize";
import { useOutsideClick } from "@featherds/composables/events/OutsideClick";
import { useResize } from "@featherds/composables/events/Resize";
import { useScroll } from "@featherds/composables/events/Scroll";
var FeatherMenu_vue_vue_type_style_index_0_lang = "";
var FeatherMenu_vue_vue_type_style_index_1_scoped_true_lang = "";
import { getElements, addLayer, removeLayer } from "@featherds/composables/modal/Layers";
var FeatherMenu_vue_vue_type_style_index_0_scoped_true_lang = "";
var _export_sfc = (sfc, props) => {

@@ -16,3 +16,3 @@ const target = sfc.__vccOpts || sfc;

const _sfc_main = {
emits: ["outside-click", "trigger-click", "close"],
emits: ["trigger-click", "close", "outside-click"],
props: {

@@ -34,2 +34,6 @@ open: {

default: false
},
hasFocus: {
type: Boolean,
default: false
}

@@ -41,47 +45,22 @@ },

const menu = ref(null);
const scrollX = ref(null);
const scrollY = ref(null);
const menuWidth = ref("auto");
const windowRef = ref(window);
const triggerId = ref(getSafeId("feather-menu-trigger"));
const menuId = ref(getSafeId("feather-menu-dropdown"));
const position = ref(`bottom-${props.right ? "right" : "left"}`);
const covered = computed(() => props.cover ? "covered" : "");
const updateScrollElements = () => {
let node = root.value.parentNode;
scrollX.value = null;
scrollY.value = null;
while (node) {
if (node === document.body) {
if (!scrollX.value) {
scrollX.value = document;
}
if (!scrollY.value) {
scrollY.value = document;
}
return;
}
if (!scrollY.value && node.scrollHeight > node.clientHeight) {
const overflowYStyle = window.getComputedStyle(node).overflowY;
if (overflowYStyle.indexOf("hidden") === -1 && overflowYStyle.indexOf("visible") === -1) {
scrollY.value = node;
}
}
if (!scrollX.value && node.scrollWidth > node.clientWidth) {
const overflowXStyle = window.getComputedStyle(node).overflowX;
if (overflowXStyle.indexOf("hidden") === -1 && overflowXStyle.indexOf("visible") === -1) {
scrollX.value = node;
}
}
if (scrollY.value && scrollX.value) {
return;
}
node = node.parentNode;
}
const position = ref("");
const scrollTop = () => {
if (!document)
return 0;
return (document.documentElement || document.body).scrollTop;
};
const getScrollRect = (element) => {
if (element && element.getBoundingClientRect) {
return element.getBoundingClientRect();
}
const scrollLeft = () => {
if (!document)
return 0;
return (document.documentElement || document.body).scrollLeft;
};
const calculating = ref(false);
const getScrollRect = () => {
return {
height: window.innerHeight,
width: window.innerWidth,
height: windowRef.value.innerHeight,
width: windowRef.value.innerWidth,
left: 0,

@@ -92,47 +71,72 @@ top: 0

const calculatePosition = () => {
updateScrollElements();
const rect = trigger.value.getBoundingClientRect();
if (!menu.value) {
return;
}
const containerRect = root.value.getBoundingClientRect();
calculating.value = true;
nextTick(() => {
const { height, width } = menu.value.getBoundingClientRect();
const scrollYRect = getScrollRect(scrollY.value);
let scrollXRect = scrollYRect;
if (scrollY.value !== scrollX.value) {
scrollXRect = getScrollRect(scrollX.value);
let { height, width } = menu.value.getBoundingClientRect();
const windowRect = getScrollRect();
const scrollHeight = windowRect.height;
const scrollWidth = windowRect.width;
if (width < containerRect.width) {
menuWidth.value = containerRect.width + "px";
width = containerRect.width;
} else {
menuWidth.value = width + "px";
}
const scrollHeight = scrollYRect.height;
const scrollWidth = scrollXRect.width;
const adjustedRect = {
bottom: rect.bottom - scrollYRect.top,
top: rect.top - scrollYRect.top,
left: rect.left - scrollXRect.left,
right: rect.right - scrollXRect.left
};
let vertical = "bottom";
if (scrollHeight - adjustedRect.bottom < height && adjustedRect.top >= height) {
vertical = "top";
let top = 0;
if (scrollHeight - containerRect.bottom < height && containerRect.top >= height) {
top = containerRect.top - height;
if (props.cover) {
top += containerRect.height;
}
} else {
top = containerRect.bottom;
if (props.cover) {
top -= containerRect.height;
}
}
let horizontal = props.right ? "right" : "left";
if (!props.right && adjustedRect.right >= width && scrollWidth - adjustedRect.left < width) {
horizontal = "right";
top += scrollTop();
let left = props.right ? containerRect.right - width : containerRect.left;
if (!props.right && containerRect.right >= width && scrollWidth - containerRect.left < width) {
left = containerRect.right - width;
}
if (props.right && adjustedRect.right <= width && scrollWidth - adjustedRect.left > width) {
horizontal = "left";
if (props.right && containerRect.right <= width && scrollWidth - containerRect.left > width) {
left = containerRect.left;
}
position.value = `${vertical}-${horizontal}`;
left += scrollLeft();
position.value = `translate(${left}px, ${top}px)`;
calculating.value = false;
});
};
const close = (e) => {
if (layers.value.some((el) => el.contains(e.target))) {
return;
}
context.emit("close", false);
};
const outsideElementEvent = (e) => {
context.emit("outside-click", e);
};
const activateOutsideClick = useOutsideClick(root, outsideElementEvent);
const activateResize = useResize(calculatePosition);
const activateScrollX = useScroll(scrollX, calculatePosition);
const activateScrollY = useScroll(scrollY, calculatePosition);
watch(() => props.open, (v) => {
if (v) {
const layer = ref(null);
const layers = computed(() => {
if (layer.value) {
return [root.value, ...getElements(layer.value).value];
}
return [root.value];
});
const activateOutsideClick = useOutsideClick(layers, outsideElementEvent);
const activateResize = useResize(close);
const activateScrollY = useScroll(windowRef, close);
watch([() => props.open, menu], ([v, m]) => {
if (v && m && !layer.value) {
calculatePosition();
layer.value = addLayer(menu, "dropdown");
} else if (!v && layer.value) {
removeLayer(layer.value);
layer.value = null;
}
activateOutsideClick.value = v;
activateResize.value = v;
activateScrollX.value = v;
activateScrollY.value = v;

@@ -160,11 +164,16 @@ });

});
const handleFocusOut = () => {
context.emit("close");
};
return {
position,
covered,
triggerId,
menuId,
menu,
menuWidth,
root,
trigger,
calculatePosition
calculatePosition,
handleFocusOut,
calculating
};

@@ -181,14 +190,25 @@ }

renderSlot(_ctx.$slots, "trigger", {}, void 0, true),
withDirectives(createElementVNode("div", {
class: normalizeClass(["feather-menu-dropdown", [$setup.position, $setup.covered]]),
ref: "menu",
id: $setup.menuId
}, [
renderSlot(_ctx.$slots, "default", { labelId: $setup.triggerId }, void 0, true)
], 10, _hoisted_2), [
[vShow, $props.open]
])
(openBlock(), createBlock(Teleport, { to: "body" }, [
withDirectives(createElementVNode("div", {
class: normalizeClass(["feather-menu-dropdown", { hidden: $setup.calculating }]),
ref: "menu",
id: $setup.menuId,
style: normalizeStyle({ transform: $setup.position, width: $setup.menuWidth })
}, [
createElementVNode("div", {
tabindex: "0",
onFocus: _cache[0] || (_cache[0] = (...args) => $setup.handleFocusOut && $setup.handleFocusOut(...args))
}, null, 32),
renderSlot(_ctx.$slots, "default", { labelId: $setup.triggerId }, void 0, true),
createElementVNode("div", {
tabindex: "0",
onFocus: _cache[1] || (_cache[1] = (...args) => $setup.handleFocusOut && $setup.handleFocusOut(...args))
}, null, 32)
], 14, _hoisted_2), [
[vShow, $props.open]
])
]))
], 512);
}
var FeatherMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-0e29b938"]]);
var FeatherMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-02a45add"]]);
var MenuFocusLoop = {

@@ -195,0 +215,0 @@ mounted(el, binding) {

{
"name": "@featherds/menu",
"version": "0.9.6",
"version": "0.10.0",
"publishConfig": {

@@ -12,6 +12,6 @@ "access": "public"

"dependencies": {
"@featherds/composables": "^0.9.6",
"@featherds/ripple": "^0.9.6",
"@featherds/styles": "^0.9.6",
"@featherds/utils": "^0.9.6",
"@featherds/composables": "^0.10.0",
"@featherds/ripple": "^0.10.0",
"@featherds/styles": "^0.10.0",
"@featherds/utils": "^0.10.0",
"vue": "^3.1.0-0"

@@ -24,3 +24,3 @@ },

"types": "./src/index.d.ts",
"gitHead": "04a74207e8bfa8d39acc470365e30dd6c90b2e8e"
"gitHead": "9871b17eaedcfc90174b78b21acb0cc06afd594c"
}

@@ -98,12 +98,3 @@ import { nextTick } from "vue";

});
it("should add covered class for cover property", async () => {
const slots = {
trigger: getTrigger(),
default: [getContent()],
};
const wrapper = await getWrapper({ slots, props: { cover: true } });
expect(
wrapper.find(".feather-menu-dropdown").classes().includes("covered")
).toBe(true);
});
describe("calculatePosition", () => {

@@ -126,6 +117,3 @@ describe("right", () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -141,3 +129,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("bottom-right");
expect(wrapper.vm.position).toBe("translate(120px, 60px)");
});

@@ -162,6 +150,3 @@ it("should flip to top right if not bottom space and enough top space", async () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -177,3 +162,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("top-right");
expect(wrapper.vm.position).toBe("translate(120px, 678px)");
});

@@ -197,6 +182,3 @@ it("should flip to bottom left if not right space and enough left space", async () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -212,3 +194,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("bottom-left");
expect(wrapper.vm.position).toBe("translate(30px, 60px)");
});

@@ -234,6 +216,3 @@ it("should stay bottom right if not enough room anywhere", async () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -249,3 +228,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("bottom-right");
expect(wrapper.vm.position).toBe("translate(974px, 718px)");
});

@@ -271,6 +250,3 @@ it("should flip to top left if there is no space right and bottom but enough top and left", async () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -286,3 +262,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("top-left");
expect(wrapper.vm.position).toBe("translate(10px, 678px)");
});

@@ -306,6 +282,3 @@ });

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -321,3 +294,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("bottom-left");
expect(wrapper.vm.position).toBe("translate(50px, 60px)");
});

@@ -342,6 +315,3 @@ it("should flip to top left if not bottom space and enough top space", async () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -357,3 +327,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("top-left");
expect(wrapper.vm.position).toBe("translate(50px, 678px)");
});

@@ -378,6 +348,3 @@ it("should flip to bottom right if not right space and enough right space", async () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -393,3 +360,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("bottom-right");
expect(wrapper.vm.position).toBe("translate(994px, 60px)");
});

@@ -415,6 +382,3 @@ it("should stay bottom left if not enough room anywhere", async () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -430,3 +394,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("bottom-left");
expect(wrapper.vm.position).toBe("translate(50px, 718px)");
});

@@ -452,6 +416,3 @@ it("should flip to top right if there is no space right and bottom but enough top and left", async () => {

jest
.spyOn(
wrapper.find("[menu-trigger]").element,
"getBoundingClientRect"
)
.spyOn(wrapper.vm.$refs.root, "getBoundingClientRect")
.mockImplementation(() => {

@@ -467,3 +428,3 @@ return {

await nextTick();
expect(wrapper.vm.position).toBe("top-right");
expect(wrapper.vm.position).toBe("translate(994px, 678px)");
});

@@ -479,3 +440,3 @@ });

const wrapper = await getWrapper({ slots });
await wrapper.setProps({ open: true });
wrapper.setProps({ open: true });
expect(await axe(wrapper.element)).toHaveNoViolations();

@@ -482,0 +443,0 @@ });

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc