@vuu-ui/vuu-popups 2.1.19-beta.2 → 2.1.19-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/index.js +18 -0
- package/cjs/index.js.map +1 -0
- package/cjs/popup/Popup.css.js +6 -0
- package/cjs/popup/Popup.css.js.map +1 -0
- package/cjs/popup/Popup.js +38 -0
- package/cjs/popup/Popup.js.map +1 -0
- package/cjs/popup/getPositionRelativeToAnchor.js +43 -0
- package/cjs/popup/getPositionRelativeToAnchor.js.map +1 -0
- package/cjs/popup/useAnchoredPosition.js +58 -0
- package/cjs/popup/useAnchoredPosition.js.map +1 -0
- package/cjs/popup-menu/PopupMenu.css.js +6 -0
- package/cjs/popup-menu/PopupMenu.css.js.map +1 -0
- package/cjs/popup-menu/PopupMenu.js +100 -0
- package/cjs/popup-menu/PopupMenu.js.map +1 -0
- package/cjs/popup-menu/usePopupMenu.js +100 -0
- package/cjs/popup-menu/usePopupMenu.js.map +1 -0
- package/cjs/portal/Portal.css.js +6 -0
- package/cjs/portal/Portal.css.js.map +1 -0
- package/cjs/portal/Portal.js +73 -0
- package/cjs/portal/Portal.js.map +1 -0
- package/cjs/tooltip/Tooltip.css.js +6 -0
- package/cjs/tooltip/Tooltip.css.js.map +1 -0
- package/cjs/tooltip/Tooltip.js +56 -0
- package/cjs/tooltip/Tooltip.js.map +1 -0
- package/cjs/tooltip/useTooltip.js +106 -0
- package/cjs/tooltip/useTooltip.js.map +1 -0
- package/cjs/tooltip/useTooltipAnchoredPosition.js +117 -0
- package/cjs/tooltip/useTooltipAnchoredPosition.js.map +1 -0
- package/esm/index.js +7 -0
- package/esm/index.js.map +1 -0
- package/esm/popup/Popup.css.js +4 -0
- package/esm/popup/Popup.css.js.map +1 -0
- package/esm/popup/Popup.js +36 -0
- package/esm/popup/Popup.js.map +1 -0
- package/esm/popup/getPositionRelativeToAnchor.js +41 -0
- package/esm/popup/getPositionRelativeToAnchor.js.map +1 -0
- package/esm/popup/useAnchoredPosition.js +56 -0
- package/esm/popup/useAnchoredPosition.js.map +1 -0
- package/esm/popup-menu/PopupMenu.css.js +4 -0
- package/esm/popup-menu/PopupMenu.css.js.map +1 -0
- package/esm/popup-menu/PopupMenu.js +98 -0
- package/esm/popup-menu/PopupMenu.js.map +1 -0
- package/esm/popup-menu/usePopupMenu.js +98 -0
- package/esm/popup-menu/usePopupMenu.js.map +1 -0
- package/esm/portal/Portal.css.js +4 -0
- package/esm/portal/Portal.css.js.map +1 -0
- package/esm/portal/Portal.js +71 -0
- package/esm/portal/Portal.js.map +1 -0
- package/esm/tooltip/Tooltip.css.js +4 -0
- package/esm/tooltip/Tooltip.css.js.map +1 -0
- package/esm/tooltip/Tooltip.js +54 -0
- package/esm/tooltip/Tooltip.js.map +1 -0
- package/esm/tooltip/useTooltip.js +104 -0
- package/esm/tooltip/useTooltip.js.map +1 -0
- package/esm/tooltip/useTooltipAnchoredPosition.js +115 -0
- package/esm/tooltip/useTooltipAnchoredPosition.js.map +1 -0
- package/package.json +13 -10
- package/src/index.js +0 -4
- package/src/popup/Popup.js +0 -29
- package/src/popup/getPositionRelativeToAnchor.js +0 -47
- package/src/popup/index.js +0 -2
- package/src/popup/useAnchoredPosition.js +0 -41
- package/src/popup-menu/PopupMenu.css.js +0 -11
- package/src/popup-menu/PopupMenu.js +0 -67
- package/src/popup-menu/index.js +0 -1
- package/src/popup-menu/usePopupMenu.js +0 -72
- package/src/portal/Portal.css.js +0 -27
- package/src/portal/Portal.js +0 -62
- package/src/portal/index.js +0 -1
- package/src/tooltip/Tooltip.css.js +0 -76
- package/src/tooltip/Tooltip.js +0 -41
- package/src/tooltip/index.js +0 -2
- package/src/tooltip/useTooltip.js +0 -91
- package/src/tooltip/useTooltipAnchoredPosition.js +0 -102
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var vuuUtils = require('@vuu-ui/vuu-utils');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
const useTooltip = ({
|
|
7
|
+
anchorQuery = "*",
|
|
8
|
+
id: idProp,
|
|
9
|
+
placement = ["right", "above", "below"],
|
|
10
|
+
tooltipContent
|
|
11
|
+
}) => {
|
|
12
|
+
const hideTooltipRef = react.useRef(void 0);
|
|
13
|
+
const isHoveringRef = react.useRef(false);
|
|
14
|
+
const anchorElementRef = react.useRef(null);
|
|
15
|
+
const mouseEnterTimerRef = react.useRef(void 0);
|
|
16
|
+
const mouseLeaveTimerRef = react.useRef(void 0);
|
|
17
|
+
const [tooltipProps, setTooltipProps] = react.useState();
|
|
18
|
+
const id = vuuUtils.useId(idProp);
|
|
19
|
+
const escapeListener = react.useCallback((evt) => {
|
|
20
|
+
if (evt.key === "Escape") {
|
|
21
|
+
hideTooltipRef.current?.();
|
|
22
|
+
}
|
|
23
|
+
}, []);
|
|
24
|
+
hideTooltipRef.current = react.useCallback(() => {
|
|
25
|
+
setTooltipProps(void 0);
|
|
26
|
+
document.removeEventListener("keydown", escapeListener);
|
|
27
|
+
}, [escapeListener]);
|
|
28
|
+
const handleMouseEnterTooltip = react.useCallback(() => {
|
|
29
|
+
window.clearTimeout(mouseLeaveTimerRef.current);
|
|
30
|
+
}, []);
|
|
31
|
+
const handleMouseLeaveTooltip = react.useCallback(() => {
|
|
32
|
+
hideTooltipRef.current?.();
|
|
33
|
+
}, []);
|
|
34
|
+
const hideTooltip = react.useCallback((defer = 0) => {
|
|
35
|
+
if (mouseEnterTimerRef.current) {
|
|
36
|
+
window.clearTimeout(mouseEnterTimerRef.current);
|
|
37
|
+
mouseEnterTimerRef.current = void 0;
|
|
38
|
+
} else if (hideTooltipRef.current) {
|
|
39
|
+
if (defer === 0) {
|
|
40
|
+
hideTooltipRef.current();
|
|
41
|
+
} else {
|
|
42
|
+
mouseLeaveTimerRef.current = window.setTimeout(
|
|
43
|
+
hideTooltipRef.current,
|
|
44
|
+
defer
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}, []);
|
|
49
|
+
const showTooltip = react.useCallback(
|
|
50
|
+
(ref = anchorElementRef) => {
|
|
51
|
+
const { current: anchorEl } = ref;
|
|
52
|
+
if (anchorEl) {
|
|
53
|
+
setTooltipProps({
|
|
54
|
+
anchorElement: ref,
|
|
55
|
+
children: tooltipContent,
|
|
56
|
+
id: `${id}-tooltip`,
|
|
57
|
+
onMouseEnter: handleMouseEnterTooltip,
|
|
58
|
+
onMouseLeave: handleMouseLeaveTooltip,
|
|
59
|
+
placement
|
|
60
|
+
});
|
|
61
|
+
document.addEventListener("keydown", escapeListener);
|
|
62
|
+
}
|
|
63
|
+
mouseEnterTimerRef.current = void 0;
|
|
64
|
+
hideTooltip(isHoveringRef.current ? 3e3 : 1e3);
|
|
65
|
+
},
|
|
66
|
+
[
|
|
67
|
+
escapeListener,
|
|
68
|
+
handleMouseEnterTooltip,
|
|
69
|
+
handleMouseLeaveTooltip,
|
|
70
|
+
hideTooltip,
|
|
71
|
+
id,
|
|
72
|
+
placement,
|
|
73
|
+
tooltipContent
|
|
74
|
+
]
|
|
75
|
+
);
|
|
76
|
+
const handleMouseEnter = react.useCallback(
|
|
77
|
+
(evt) => {
|
|
78
|
+
isHoveringRef.current = true;
|
|
79
|
+
const el = vuuUtils.queryClosest(evt.target, anchorQuery);
|
|
80
|
+
if (el) {
|
|
81
|
+
console.log(`el ${el.classList}`);
|
|
82
|
+
anchorElementRef.current = el;
|
|
83
|
+
mouseEnterTimerRef.current = window.setTimeout(showTooltip, 800);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
[anchorQuery, showTooltip]
|
|
87
|
+
);
|
|
88
|
+
const handleMouseLeave = react.useCallback(() => {
|
|
89
|
+
isHoveringRef.current = false;
|
|
90
|
+
hideTooltip(200);
|
|
91
|
+
}, [hideTooltip]);
|
|
92
|
+
const anchorProps = {
|
|
93
|
+
"aria-describedby": `${id}-tooltip`,
|
|
94
|
+
onMouseEnter: handleMouseEnter,
|
|
95
|
+
onMouseLeave: handleMouseLeave
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
anchorProps,
|
|
99
|
+
hideTooltip,
|
|
100
|
+
showTooltip,
|
|
101
|
+
tooltipProps
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
exports.useTooltip = useTooltip;
|
|
106
|
+
//# sourceMappingURL=useTooltip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTooltip.js","sources":["../../../../packages/vuu-popups/src/tooltip/useTooltip.ts"],"sourcesContent":["import { queryClosest, useId } from \"@vuu-ui/vuu-utils\";\nimport { MouseEvent, ReactNode, useCallback, useRef, useState } from \"react\";\nimport { TooltipProps } from \"./Tooltip\";\nimport { TooltipPlacement } from \"./useTooltipAnchoredPosition\";\n\nexport interface TooltipHookProps {\n anchorQuery?: string;\n id: string;\n placement?: TooltipPlacement | TooltipPlacement[];\n tooltipContent: ReactNode;\n}\n\nexport const useTooltip = ({\n anchorQuery = \"*\",\n id: idProp,\n placement = [\"right\", \"above\", \"below\"],\n tooltipContent,\n}: TooltipHookProps) => {\n const hideTooltipRef = useRef<() => void>(undefined);\n const isHoveringRef = useRef(false);\n const anchorElementRef = useRef<HTMLElement | null>(null);\n const mouseEnterTimerRef = useRef<number | undefined>(undefined);\n const mouseLeaveTimerRef = useRef<number | undefined>(undefined);\n const [tooltipProps, setTooltipProps] = useState<TooltipProps | undefined>();\n\n const id = useId(idProp);\n\n const escapeListener = useCallback((evt: KeyboardEvent) => {\n if (evt.key === \"Escape\") {\n hideTooltipRef.current?.();\n }\n }, []);\n\n hideTooltipRef.current = useCallback(() => {\n setTooltipProps(undefined);\n document.removeEventListener(\"keydown\", escapeListener);\n }, [escapeListener]);\n\n const handleMouseEnterTooltip = useCallback(() => {\n window.clearTimeout(mouseLeaveTimerRef.current);\n }, []);\n\n const handleMouseLeaveTooltip = useCallback(() => {\n hideTooltipRef.current?.();\n }, []);\n\n const hideTooltip = useCallback((defer = 0) => {\n if (mouseEnterTimerRef.current) {\n window.clearTimeout(mouseEnterTimerRef.current);\n mouseEnterTimerRef.current = undefined;\n } else if (hideTooltipRef.current) {\n if (defer === 0) {\n hideTooltipRef.current();\n } else {\n mouseLeaveTimerRef.current = window.setTimeout(\n hideTooltipRef.current,\n defer,\n );\n }\n }\n }, []);\n\n const showTooltip = useCallback(\n (ref = anchorElementRef) => {\n const { current: anchorEl } = ref;\n if (anchorEl) {\n setTooltipProps({\n anchorElement: ref,\n children: tooltipContent,\n id: `${id}-tooltip`,\n onMouseEnter: handleMouseEnterTooltip,\n onMouseLeave: handleMouseLeaveTooltip,\n placement: placement,\n });\n // register ESC listener\n document.addEventListener(\"keydown\", escapeListener);\n }\n mouseEnterTimerRef.current = undefined;\n hideTooltip(isHoveringRef.current ? 3000 : 1000);\n },\n [\n escapeListener,\n handleMouseEnterTooltip,\n handleMouseLeaveTooltip,\n hideTooltip,\n id,\n placement,\n tooltipContent,\n ],\n );\n\n const handleMouseEnter = useCallback(\n (evt: MouseEvent) => {\n isHoveringRef.current = true;\n const el = queryClosest(evt.target, anchorQuery);\n if (el) {\n console.log(`el ${el.classList}`);\n anchorElementRef.current = el;\n mouseEnterTimerRef.current = window.setTimeout(showTooltip, 800);\n }\n },\n [anchorQuery, showTooltip],\n );\n\n const handleMouseLeave = useCallback(() => {\n isHoveringRef.current = false;\n hideTooltip(200);\n }, [hideTooltip]);\n\n const anchorProps = {\n \"aria-describedby\": `${id}-tooltip`,\n onMouseEnter: handleMouseEnter,\n onMouseLeave: handleMouseLeave,\n };\n\n return {\n anchorProps,\n hideTooltip,\n showTooltip,\n tooltipProps,\n };\n};\n"],"names":["useRef","useState","useId","useCallback","queryClosest"],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
|
|
5
|
+
const pointerSize = 12;
|
|
6
|
+
const roomAbove = (anchor, tooltip) => tooltip.height + pointerSize < anchor.top;
|
|
7
|
+
const roomLeft = (anchor, tooltip) => tooltip.width + pointerSize < anchor.left;
|
|
8
|
+
const roomRight = (anchor, tooltip) => anchor.right + tooltip.width + pointerSize < document.body.clientWidth;
|
|
9
|
+
const roomBelow = (anchor, tooltip) => document.body.clientHeight - anchor.bottom > tooltip.height + pointerSize;
|
|
10
|
+
const roomAvailableAtPlacement = (placement, anchor, tooltip) => {
|
|
11
|
+
switch (placement) {
|
|
12
|
+
case "above":
|
|
13
|
+
return roomAbove(anchor, tooltip);
|
|
14
|
+
case "left":
|
|
15
|
+
return roomLeft(anchor, tooltip);
|
|
16
|
+
case "below":
|
|
17
|
+
return roomBelow(anchor, tooltip);
|
|
18
|
+
case "right":
|
|
19
|
+
return roomRight(anchor, tooltip);
|
|
20
|
+
default:
|
|
21
|
+
throw Error("invalid tooltip placement");
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const positionAbove = (anchor, tooltip) => ({
|
|
25
|
+
left: anchor.left - (tooltip.width - anchor.width) / 2,
|
|
26
|
+
top: anchor.top - (tooltip.height + pointerSize)
|
|
27
|
+
});
|
|
28
|
+
const positionBelow = (anchor, tooltip) => ({
|
|
29
|
+
left: anchor.left - (tooltip.width - anchor.width) / 2,
|
|
30
|
+
top: anchor.bottom + pointerSize
|
|
31
|
+
});
|
|
32
|
+
const positionLeft = (anchor, tooltip) => ({
|
|
33
|
+
left: anchor.left - pointerSize - tooltip.width,
|
|
34
|
+
top: anchor.top - (tooltip.height - anchor.height) / 2
|
|
35
|
+
});
|
|
36
|
+
const positionRight = (anchor, tooltip) => ({
|
|
37
|
+
left: anchor.right + pointerSize,
|
|
38
|
+
top: anchor.top - (tooltip.height - anchor.height) / 2
|
|
39
|
+
});
|
|
40
|
+
const positionAtPlacement = (placement, anchor, tooltip) => {
|
|
41
|
+
switch (placement) {
|
|
42
|
+
case "above":
|
|
43
|
+
return positionAbove(anchor, tooltip);
|
|
44
|
+
case "left":
|
|
45
|
+
return positionLeft(anchor, tooltip);
|
|
46
|
+
case "below":
|
|
47
|
+
return positionBelow(anchor, tooltip);
|
|
48
|
+
case "right":
|
|
49
|
+
return positionRight(anchor, tooltip);
|
|
50
|
+
default:
|
|
51
|
+
throw Error("invalid tooltip placement");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const keepWithinTheScreen = ({ height, width }, position) => {
|
|
55
|
+
const { clientWidth, clientHeight } = document.body;
|
|
56
|
+
let { left, top } = position;
|
|
57
|
+
if (left + width > clientWidth) {
|
|
58
|
+
left -= left + width - clientWidth;
|
|
59
|
+
}
|
|
60
|
+
if (left < 0) {
|
|
61
|
+
left = 0;
|
|
62
|
+
}
|
|
63
|
+
if (top + height > clientHeight) {
|
|
64
|
+
top -= top + height - clientHeight;
|
|
65
|
+
}
|
|
66
|
+
if (top < 0) {
|
|
67
|
+
top = 0;
|
|
68
|
+
}
|
|
69
|
+
return { left, top };
|
|
70
|
+
};
|
|
71
|
+
const toCSSText = ({ left, top }) => `left:${left}px;top:${top}px;opacity:1;`;
|
|
72
|
+
const getNextPlacement = (placement) => {
|
|
73
|
+
if (Array.isArray(placement)) {
|
|
74
|
+
if (placement.length === 0) {
|
|
75
|
+
return [void 0, placement];
|
|
76
|
+
} else {
|
|
77
|
+
return [placement[0], placement.slice(1)];
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
return [placement, []];
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const useTooltipAnchoredPosition = ({
|
|
84
|
+
anchorElement,
|
|
85
|
+
placement
|
|
86
|
+
}) => {
|
|
87
|
+
const ref = react.useCallback(
|
|
88
|
+
(el) => {
|
|
89
|
+
if (el && anchorElement.current) {
|
|
90
|
+
const anchorRect = anchorElement.current.getBoundingClientRect();
|
|
91
|
+
const tooltipRect = el.getBoundingClientRect();
|
|
92
|
+
let nextPlacement;
|
|
93
|
+
let placements = placement;
|
|
94
|
+
[nextPlacement = "right", placements] = getNextPlacement(placements);
|
|
95
|
+
do {
|
|
96
|
+
if (roomAvailableAtPlacement(nextPlacement, anchorRect, tooltipRect)) {
|
|
97
|
+
el.style.cssText = toCSSText(
|
|
98
|
+
keepWithinTheScreen(
|
|
99
|
+
tooltipRect,
|
|
100
|
+
positionAtPlacement(nextPlacement, anchorRect, tooltipRect)
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
el.dataset.align = nextPlacement;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
[nextPlacement, placements] = getNextPlacement(placements);
|
|
107
|
+
} while (nextPlacement);
|
|
108
|
+
}
|
|
109
|
+
el?.classList.remove("vuuHidden");
|
|
110
|
+
},
|
|
111
|
+
[anchorElement, placement]
|
|
112
|
+
);
|
|
113
|
+
return ref;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
exports.useTooltipAnchoredPosition = useTooltipAnchoredPosition;
|
|
117
|
+
//# sourceMappingURL=useTooltipAnchoredPosition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useTooltipAnchoredPosition.js","sources":["../../../../packages/vuu-popups/src/tooltip/useTooltipAnchoredPosition.ts"],"sourcesContent":["// TODO merge with Popup\n\nimport { RefCallback, RefObject, useCallback } from \"react\";\n\nexport type TooltipPlacement = \"above\" | \"right\" | \"below\" | \"left\";\n\nconst pointerSize = 12;\nexport interface TooltipAnchoredPositionHookProps {\n anchorElement: RefObject<HTMLElement | null>;\n placement: TooltipPlacement | TooltipPlacement[];\n}\n\nconst roomAbove = (anchor: DOMRect, tooltip: DOMRect) =>\n tooltip.height + pointerSize < anchor.top;\nconst roomLeft = (anchor: DOMRect, tooltip: DOMRect) =>\n tooltip.width + pointerSize < anchor.left;\nconst roomRight = (anchor: DOMRect, tooltip: DOMRect) =>\n anchor.right + tooltip.width + pointerSize < document.body.clientWidth;\nconst roomBelow = (anchor: DOMRect, tooltip: DOMRect) =>\n document.body.clientHeight - anchor.bottom > tooltip.height + pointerSize;\n\nconst roomAvailableAtPlacement = (\n placement: TooltipPlacement,\n anchor: DOMRect,\n tooltip: DOMRect,\n) => {\n switch (placement) {\n case \"above\":\n return roomAbove(anchor, tooltip);\n case \"left\":\n return roomLeft(anchor, tooltip);\n case \"below\":\n return roomBelow(anchor, tooltip);\n case \"right\":\n return roomRight(anchor, tooltip);\n default:\n throw Error(\"invalid tooltip placement\");\n }\n};\n\ntype Position = { left: number; top: number };\ntype Positioner = (anchorRect: DOMRect, tooltipRect: DOMRect) => Position;\n\nconst positionAbove: Positioner = (anchor, tooltip) => ({\n left: anchor.left - (tooltip.width - anchor.width) / 2,\n top: anchor.top - (tooltip.height + pointerSize),\n});\n\nconst positionBelow: Positioner = (anchor, tooltip) => ({\n left: anchor.left - (tooltip.width - anchor.width) / 2,\n top: anchor.bottom + pointerSize,\n});\nconst positionLeft: Positioner = (anchor, tooltip) => ({\n left: anchor.left - pointerSize - tooltip.width,\n top: anchor.top - (tooltip.height - anchor.height) / 2,\n});\n\nconst positionRight: Positioner = (anchor, tooltip) => ({\n left: anchor.right + pointerSize,\n top: anchor.top - (tooltip.height - anchor.height) / 2,\n});\n\nconst positionAtPlacement = (\n placement: TooltipPlacement,\n anchor: DOMRect,\n tooltip: DOMRect,\n) => {\n switch (placement) {\n case \"above\":\n return positionAbove(anchor, tooltip);\n case \"left\":\n return positionLeft(anchor, tooltip);\n case \"below\":\n return positionBelow(anchor, tooltip);\n case \"right\":\n return positionRight(anchor, tooltip);\n default:\n throw Error(\"invalid tooltip placement\");\n }\n};\n\nconst keepWithinTheScreen = (\n { height, width }: DOMRect,\n position: Position,\n) => {\n const { clientWidth, clientHeight } = document.body;\n let { left, top } = position;\n if (left + width > clientWidth) {\n left -= left + width - clientWidth;\n }\n if (left < 0) {\n left = 0;\n }\n if (top + height > clientHeight) {\n top -= top + height - clientHeight;\n }\n if (top < 0) {\n top = 0;\n }\n\n return { left, top };\n};\n\nconst toCSSText = ({ left, top }: Position) =>\n `left:${left}px;top:${top}px;opacity:1;`;\n\nconst getNextPlacement = (\n placement: TooltipPlacement | TooltipPlacement[],\n): [TooltipPlacement | undefined, TooltipPlacement[]] => {\n if (Array.isArray(placement)) {\n if (placement.length === 0) {\n return [undefined, placement];\n } else {\n return [placement[0], placement.slice(1)];\n }\n } else {\n return [placement, []];\n }\n};\n\nexport const useTooltipAnchoredPosition = ({\n anchorElement,\n placement,\n}: TooltipAnchoredPositionHookProps) => {\n const ref = useCallback<RefCallback<HTMLDivElement>>(\n (el) => {\n if (el && anchorElement.current) {\n const anchorRect = anchorElement.current.getBoundingClientRect();\n const tooltipRect = el.getBoundingClientRect();\n let nextPlacement: TooltipPlacement | undefined;\n let placements: TooltipPlacement | TooltipPlacement[] = placement;\n [nextPlacement = \"right\", placements] = getNextPlacement(placements);\n do {\n if (\n roomAvailableAtPlacement(nextPlacement, anchorRect, tooltipRect)\n ) {\n el.style.cssText = toCSSText(\n keepWithinTheScreen(\n tooltipRect,\n positionAtPlacement(nextPlacement, anchorRect, tooltipRect),\n ),\n );\n el.dataset.align = nextPlacement;\n return;\n }\n [nextPlacement, placements] = getNextPlacement(placements);\n } while (nextPlacement);\n }\n el?.classList.remove(\"vuuHidden\");\n },\n [anchorElement, placement],\n );\n return ref;\n};\n"],"names":["useCallback"],"mappings":"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"}
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { PopupComponent } from './popup/Popup.js';
|
|
2
|
+
export { useAnchoredPosition } from './popup/useAnchoredPosition.js';
|
|
3
|
+
export { PopupMenu } from './popup-menu/PopupMenu.js';
|
|
4
|
+
export { Portal } from './portal/Portal.js';
|
|
5
|
+
export { Tooltip } from './tooltip/Tooltip.js';
|
|
6
|
+
export { useTooltip } from './tooltip/useTooltip.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
package/esm/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Popup.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import cx from 'clsx';
|
|
3
|
+
import { useComponentCssInjection } from '@salt-ds/styles';
|
|
4
|
+
import { useWindow } from '@salt-ds/window';
|
|
5
|
+
import { useAnchoredPosition } from './useAnchoredPosition.js';
|
|
6
|
+
import popupCss from './Popup.css.js';
|
|
7
|
+
|
|
8
|
+
const PopupComponent = ({
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
anchorElement,
|
|
12
|
+
minWidth,
|
|
13
|
+
offsetLeft,
|
|
14
|
+
offsetTop,
|
|
15
|
+
placement,
|
|
16
|
+
position: positionProp
|
|
17
|
+
}) => {
|
|
18
|
+
const targetWindow = useWindow();
|
|
19
|
+
useComponentCssInjection({
|
|
20
|
+
testId: "vuu-popup",
|
|
21
|
+
css: popupCss,
|
|
22
|
+
window: targetWindow
|
|
23
|
+
});
|
|
24
|
+
const { popupRef, position } = useAnchoredPosition({
|
|
25
|
+
anchorElement,
|
|
26
|
+
minWidth,
|
|
27
|
+
offsetLeft,
|
|
28
|
+
offsetTop,
|
|
29
|
+
placement,
|
|
30
|
+
position: positionProp
|
|
31
|
+
});
|
|
32
|
+
return position === void 0 ? null : /* @__PURE__ */ jsx("div", { className: cx(`vuuPortal`, className), ref: popupRef, style: position, children });
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export { PopupComponent };
|
|
36
|
+
//# sourceMappingURL=Popup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Popup.js","sources":["../../../../packages/vuu-popups/src/popup/Popup.tsx"],"sourcesContent":["import cx from \"clsx\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { HTMLAttributes, RefObject } from \"react\";\nimport { useAnchoredPosition } from \"./useAnchoredPosition\";\nimport { Position } from \"./getPositionRelativeToAnchor\";\n\nimport popupCss from \"./Popup.css\";\n\nexport type PopupPlacement =\n | \"absolute\"\n | \"auto\"\n | \"below\"\n | \"below-center\"\n | \"below-right\"\n | \"below-full-width\"\n | \"center\"\n | \"right\";\n\nexport interface PopupComponentProps extends HTMLAttributes<HTMLDivElement> {\n anchorElement: RefObject<HTMLElement | null>;\n // TODO this is repeated in Position\n minWidth?: number | string;\n offsetLeft?: number;\n offsetTop?: number;\n placement: PopupPlacement;\n position?: Position;\n}\n\nexport const PopupComponent = ({\n children,\n className,\n anchorElement,\n minWidth,\n offsetLeft,\n offsetTop,\n placement,\n position: positionProp,\n}: PopupComponentProps) => {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"vuu-popup\",\n css: popupCss,\n window: targetWindow,\n });\n\n const { popupRef, position } = useAnchoredPosition({\n anchorElement,\n minWidth,\n offsetLeft,\n offsetTop,\n placement,\n position: positionProp,\n });\n return position === undefined ? null : (\n <div className={cx(`vuuPortal`, className)} ref={popupRef} style={position}>\n {children}\n </div>\n );\n};\n"],"names":[],"mappings":";;;;;;;AA6BO,MAAM,iBAAiB,CAAC;AAAA,EAC7B,QAAA;AAAA,EACA,SAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAU,EAAA;AACZ,CAA2B,KAAA;AACzB,EAAA,MAAM,eAAe,SAAU,EAAA;AAC/B,EAAyB,wBAAA,CAAA;AAAA,IACvB,MAAQ,EAAA,WAAA;AAAA,IACR,GAAK,EAAA,QAAA;AAAA,IACL,MAAQ,EAAA;AAAA,GACT,CAAA;AAED,EAAA,MAAM,EAAE,QAAA,EAAU,QAAS,EAAA,GAAI,mBAAoB,CAAA;AAAA,IACjD,aAAA;AAAA,IACA,QAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAA;AAAA,IACA,QAAU,EAAA;AAAA,GACX,CAAA;AACD,EAAA,OAAO,QAAa,KAAA,KAAA,CAAA,GAAY,IAC9B,mBAAA,GAAA,CAAC,SAAI,SAAW,EAAA,EAAA,CAAG,CAAa,SAAA,CAAA,EAAA,SAAS,CAAG,EAAA,GAAA,EAAK,QAAU,EAAA,KAAA,EAAO,UAC/D,QACH,EAAA,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const getPositionRelativeToAnchor = (anchorElement, placement, offsetLeft, offsetTop, minWidth, dimensions) => {
|
|
2
|
+
const { bottom, height, left, right, top, width } = anchorElement.getBoundingClientRect();
|
|
3
|
+
switch (placement) {
|
|
4
|
+
case "below":
|
|
5
|
+
return { left: left + offsetLeft, top: bottom + offsetTop };
|
|
6
|
+
case "right":
|
|
7
|
+
return { left: right + offsetLeft, top: top + offsetTop };
|
|
8
|
+
case "below-center":
|
|
9
|
+
return { left: left + width / 2 + offsetLeft, top: bottom + offsetTop };
|
|
10
|
+
case "below-right":
|
|
11
|
+
return { left, minWidth, top: bottom + offsetTop };
|
|
12
|
+
case "below-full-width":
|
|
13
|
+
return {
|
|
14
|
+
left: left + offsetLeft,
|
|
15
|
+
minWidth,
|
|
16
|
+
top: bottom + offsetTop,
|
|
17
|
+
width
|
|
18
|
+
};
|
|
19
|
+
case "center":
|
|
20
|
+
if (dimensions) {
|
|
21
|
+
return {
|
|
22
|
+
left: width / 2 - dimensions.width / 2 + offsetLeft,
|
|
23
|
+
top: height / 2 - dimensions.height / 2 + offsetTop,
|
|
24
|
+
visibility: "visible"
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
return {
|
|
28
|
+
left: width / 2 + offsetLeft,
|
|
29
|
+
top: height / 2 + offsetTop,
|
|
30
|
+
visibility: "hidden"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
default:
|
|
34
|
+
throw Error(
|
|
35
|
+
`Popup getPositionRelativeToAnchor non-supported placement value ${placement}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { getPositionRelativeToAnchor };
|
|
41
|
+
//# sourceMappingURL=getPositionRelativeToAnchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getPositionRelativeToAnchor.js","sources":["../../../../packages/vuu-popups/src/popup/getPositionRelativeToAnchor.ts"],"sourcesContent":["import { PopupPlacement } from \"./Popup\";\n\nexport type Visibility = \"hidden\" | \"visible\";\n\nexport type Position = {\n left: number;\n minWidth?: number | string;\n top: number;\n visibility?: Visibility;\n width?: number;\n};\n\nexport const getPositionRelativeToAnchor = (\n anchorElement: HTMLElement,\n placement: PopupPlacement,\n offsetLeft: number,\n offsetTop: number,\n minWidth?: number | string,\n dimensions?: { height: number; width: number }\n): {\n left: number;\n minWidth?: number | string;\n top: number;\n visibility?: Visibility;\n width?: number;\n} => {\n const { bottom, height, left, right, top, width } =\n anchorElement.getBoundingClientRect();\n switch (placement) {\n case \"below\":\n return { left: left + offsetLeft, top: bottom + offsetTop };\n case \"right\":\n return { left: right + offsetLeft, top: top + offsetTop };\n case \"below-center\":\n return { left: left + width / 2 + offsetLeft, top: bottom + offsetTop };\n case \"below-right\":\n return { left: left, minWidth, top: bottom + offsetTop };\n case \"below-full-width\":\n return {\n left: left + offsetLeft,\n minWidth,\n top: bottom + offsetTop,\n width,\n };\n case \"center\":\n if (dimensions) {\n return {\n left: width / 2 - dimensions.width / 2 + offsetLeft,\n top: height / 2 - dimensions.height / 2 + offsetTop,\n visibility: \"visible\",\n };\n } else {\n return {\n left: width / 2 + offsetLeft,\n top: height / 2 + offsetTop,\n visibility: \"hidden\",\n };\n }\n default:\n throw Error(\n `Popup getPositionRelativeToAnchor non-supported placement value ${placement}`\n );\n }\n};\n"],"names":[],"mappings":"AAYO,MAAM,8BAA8B,CACzC,aAAA,EACA,WACA,UACA,EAAA,SAAA,EACA,UACA,UAOG,KAAA;AACH,EAAM,MAAA,EAAE,QAAQ,MAAQ,EAAA,IAAA,EAAM,OAAO,GAAK,EAAA,KAAA,EACxC,GAAA,aAAA,CAAc,qBAAsB,EAAA;AACtC,EAAA,QAAQ,SAAW;AAAA,IACjB,KAAK,OAAA;AACH,MAAA,OAAO,EAAE,IAAM,EAAA,IAAA,GAAO,UAAY,EAAA,GAAA,EAAK,SAAS,SAAU,EAAA;AAAA,IAC5D,KAAK,OAAA;AACH,MAAA,OAAO,EAAE,IAAM,EAAA,KAAA,GAAQ,UAAY,EAAA,GAAA,EAAK,MAAM,SAAU,EAAA;AAAA,IAC1D,KAAK,cAAA;AACH,MAAO,OAAA,EAAE,MAAM,IAAO,GAAA,KAAA,GAAQ,IAAI,UAAY,EAAA,GAAA,EAAK,SAAS,SAAU,EAAA;AAAA,IACxE,KAAK,aAAA;AACH,MAAA,OAAO,EAAE,IAAA,EAAY,QAAU,EAAA,GAAA,EAAK,SAAS,SAAU,EAAA;AAAA,IACzD,KAAK,kBAAA;AACH,MAAO,OAAA;AAAA,QACL,MAAM,IAAO,GAAA,UAAA;AAAA,QACb,QAAA;AAAA,QACA,KAAK,MAAS,GAAA,SAAA;AAAA,QACd;AAAA,OACF;AAAA,IACF,KAAK,QAAA;AACH,MAAA,IAAI,UAAY,EAAA;AACd,QAAO,OAAA;AAAA,UACL,IAAM,EAAA,KAAA,GAAQ,CAAI,GAAA,UAAA,CAAW,QAAQ,CAAI,GAAA,UAAA;AAAA,UACzC,GAAK,EAAA,MAAA,GAAS,CAAI,GAAA,UAAA,CAAW,SAAS,CAAI,GAAA,SAAA;AAAA,UAC1C,UAAY,EAAA;AAAA,SACd;AAAA,OACK,MAAA;AACL,QAAO,OAAA;AAAA,UACL,IAAA,EAAM,QAAQ,CAAI,GAAA,UAAA;AAAA,UAClB,GAAA,EAAK,SAAS,CAAI,GAAA,SAAA;AAAA,UAClB,UAAY,EAAA;AAAA,SACd;AAAA;AACF,IACF;AACE,MAAM,MAAA,KAAA;AAAA,QACJ,mEAAmE,SAAS,CAAA;AAAA,OAC9E;AAAA;AAEN;;;;"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useRef, useState, useLayoutEffect, useCallback } from 'react';
|
|
2
|
+
import { getPositionRelativeToAnchor } from './getPositionRelativeToAnchor.js';
|
|
3
|
+
|
|
4
|
+
const useAnchoredPosition = ({
|
|
5
|
+
anchorElement,
|
|
6
|
+
minWidth,
|
|
7
|
+
offsetLeft = 0,
|
|
8
|
+
offsetTop = 0,
|
|
9
|
+
placement,
|
|
10
|
+
position: positionProp
|
|
11
|
+
}) => {
|
|
12
|
+
const popupRef = useRef(null);
|
|
13
|
+
const [position, setPosition] = useState(positionProp);
|
|
14
|
+
useLayoutEffect(() => {
|
|
15
|
+
if (placement === "absolute" && positionProp) {
|
|
16
|
+
setPosition(positionProp);
|
|
17
|
+
} else if (anchorElement.current && placement !== "auto") {
|
|
18
|
+
const dimensions = popupRef.current === null ? void 0 : popupRef.current.getBoundingClientRect();
|
|
19
|
+
const position2 = getPositionRelativeToAnchor(
|
|
20
|
+
anchorElement.current,
|
|
21
|
+
placement,
|
|
22
|
+
offsetLeft,
|
|
23
|
+
offsetTop,
|
|
24
|
+
minWidth,
|
|
25
|
+
dimensions
|
|
26
|
+
);
|
|
27
|
+
setPosition(position2);
|
|
28
|
+
}
|
|
29
|
+
}, [anchorElement, minWidth, offsetLeft, offsetTop, placement, positionProp]);
|
|
30
|
+
const popupCallbackRef = useCallback(
|
|
31
|
+
(el) => {
|
|
32
|
+
popupRef.current = el;
|
|
33
|
+
if (el && placement === "center" && anchorElement.current) {
|
|
34
|
+
const { height, width } = el.getBoundingClientRect();
|
|
35
|
+
setPosition(
|
|
36
|
+
getPositionRelativeToAnchor(
|
|
37
|
+
anchorElement.current,
|
|
38
|
+
placement,
|
|
39
|
+
offsetLeft,
|
|
40
|
+
offsetTop,
|
|
41
|
+
void 0,
|
|
42
|
+
{ height, width }
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
[anchorElement, offsetLeft, offsetTop, placement]
|
|
48
|
+
);
|
|
49
|
+
return {
|
|
50
|
+
position,
|
|
51
|
+
popupRef: placement === "center" ? popupCallbackRef : void 0
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export { useAnchoredPosition };
|
|
56
|
+
//# sourceMappingURL=useAnchoredPosition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAnchoredPosition.js","sources":["../../../../packages/vuu-popups/src/popup/useAnchoredPosition.ts"],"sourcesContent":["import { useCallback, useLayoutEffect, useRef, useState } from \"react\";\nimport {\n getPositionRelativeToAnchor,\n Position,\n} from \"./getPositionRelativeToAnchor\";\nimport { PopupComponentProps } from \"./Popup\";\n\nexport type AnchoredPositionHookProps = Pick<\n PopupComponentProps,\n | \"anchorElement\"\n | \"minWidth\"\n | \"offsetLeft\"\n | \"offsetTop\"\n | \"placement\"\n | \"position\"\n>;\n\nexport const useAnchoredPosition = ({\n anchorElement,\n minWidth,\n offsetLeft = 0,\n offsetTop = 0,\n placement,\n position: positionProp,\n}: AnchoredPositionHookProps) => {\n const popupRef = useRef<HTMLElement | null>(null);\n const [position, setPosition] = useState<Position | undefined>(positionProp);\n\n // maybe better as useMemo ?\n useLayoutEffect(() => {\n if (placement === \"absolute\" && positionProp) {\n setPosition(positionProp);\n } else if (anchorElement.current && placement !== \"auto\") {\n const dimensions =\n popupRef.current === null\n ? undefined\n : popupRef.current.getBoundingClientRect();\n const position = getPositionRelativeToAnchor(\n anchorElement.current,\n placement,\n offsetLeft,\n offsetTop,\n minWidth,\n dimensions\n );\n setPosition(position);\n }\n }, [anchorElement, minWidth, offsetLeft, offsetTop, placement, positionProp]);\n\n const popupCallbackRef = useCallback(\n (el: HTMLDivElement | null) => {\n popupRef.current = el;\n if (el && placement === \"center\" && anchorElement.current) {\n const { height, width } = el.getBoundingClientRect();\n setPosition(\n getPositionRelativeToAnchor(\n anchorElement.current,\n placement,\n offsetLeft,\n offsetTop,\n undefined,\n { height, width }\n )\n );\n }\n },\n [anchorElement, offsetLeft, offsetTop, placement]\n );\n\n return {\n position,\n popupRef: placement === \"center\" ? popupCallbackRef : undefined,\n };\n};\n"],"names":["position"],"mappings":";;;AAiBO,MAAM,sBAAsB,CAAC;AAAA,EAClC,aAAA;AAAA,EACA,QAAA;AAAA,EACA,UAAa,GAAA,CAAA;AAAA,EACb,SAAY,GAAA,CAAA;AAAA,EACZ,SAAA;AAAA,EACA,QAAU,EAAA;AACZ,CAAiC,KAAA;AAC/B,EAAM,MAAA,QAAA,GAAW,OAA2B,IAAI,CAAA;AAChD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAA+B,YAAY,CAAA;AAG3E,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAI,IAAA,SAAA,KAAc,cAAc,YAAc,EAAA;AAC5C,MAAA,WAAA,CAAY,YAAY,CAAA;AAAA,KACf,MAAA,IAAA,aAAA,CAAc,OAAW,IAAA,SAAA,KAAc,MAAQ,EAAA;AACxD,MAAA,MAAM,aACJ,QAAS,CAAA,OAAA,KAAY,OACjB,KACA,CAAA,GAAA,QAAA,CAAS,QAAQ,qBAAsB,EAAA;AAC7C,MAAA,MAAMA,SAAW,GAAA,2BAAA;AAAA,QACf,aAAc,CAAA,OAAA;AAAA,QACd,SAAA;AAAA,QACA,UAAA;AAAA,QACA,SAAA;AAAA,QACA,QAAA;AAAA,QACA;AAAA,OACF;AACA,MAAA,WAAA,CAAYA,SAAQ,CAAA;AAAA;AACtB,GACF,EAAG,CAAC,aAAe,EAAA,QAAA,EAAU,YAAY,SAAW,EAAA,SAAA,EAAW,YAAY,CAAC,CAAA;AAE5E,EAAA,MAAM,gBAAmB,GAAA,WAAA;AAAA,IACvB,CAAC,EAA8B,KAAA;AAC7B,MAAA,QAAA,CAAS,OAAU,GAAA,EAAA;AACnB,MAAA,IAAI,EAAM,IAAA,SAAA,KAAc,QAAY,IAAA,aAAA,CAAc,OAAS,EAAA;AACzD,QAAA,MAAM,EAAE,MAAA,EAAQ,KAAM,EAAA,GAAI,GAAG,qBAAsB,EAAA;AACnD,QAAA,WAAA;AAAA,UACE,2BAAA;AAAA,YACE,aAAc,CAAA,OAAA;AAAA,YACd,SAAA;AAAA,YACA,UAAA;AAAA,YACA,SAAA;AAAA,YACA,KAAA,CAAA;AAAA,YACA,EAAE,QAAQ,KAAM;AAAA;AAClB,SACF;AAAA;AACF,KACF;AAAA,IACA,CAAC,aAAA,EAAe,UAAY,EAAA,SAAA,EAAW,SAAS;AAAA,GAClD;AAEA,EAAO,OAAA;AAAA,IACL,QAAA;AAAA,IACA,QAAA,EAAU,SAAc,KAAA,QAAA,GAAW,gBAAmB,GAAA,KAAA;AAAA,GACxD;AACF;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PopupMenu.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { Button } from '@salt-ds/core';
|
|
3
|
+
import { useComponentCssInjection } from '@salt-ds/styles';
|
|
4
|
+
import { useWindow } from '@salt-ds/window';
|
|
5
|
+
import { Icon, IconButton } from '@vuu-ui/vuu-ui-controls';
|
|
6
|
+
import { useId } from '@vuu-ui/vuu-utils';
|
|
7
|
+
import cx from 'clsx';
|
|
8
|
+
import { usePopupMenu } from './usePopupMenu.js';
|
|
9
|
+
import popupMenuCss from './PopupMenu.css.js';
|
|
10
|
+
|
|
11
|
+
const classBase = "vuuPopupMenu";
|
|
12
|
+
const PopupMenu = ({
|
|
13
|
+
anchorElement,
|
|
14
|
+
"aria-label": ariaLabel,
|
|
15
|
+
appearance = "transparent",
|
|
16
|
+
className,
|
|
17
|
+
disabled = false,
|
|
18
|
+
label,
|
|
19
|
+
icon = label ? "chevron-down" : "more-vert",
|
|
20
|
+
id: idProp,
|
|
21
|
+
menuActionHandler,
|
|
22
|
+
menuBuilder,
|
|
23
|
+
menuClassName,
|
|
24
|
+
menuLocation = "header",
|
|
25
|
+
menuOptions,
|
|
26
|
+
onMenuClose,
|
|
27
|
+
onMenuOpen,
|
|
28
|
+
popupPlacement = "below-right",
|
|
29
|
+
sentiment = "neutral",
|
|
30
|
+
tabIndex = 0,
|
|
31
|
+
...htmlAttributes
|
|
32
|
+
}) => {
|
|
33
|
+
const targetWindow = useWindow();
|
|
34
|
+
useComponentCssInjection({
|
|
35
|
+
testId: "vuu-popup-menu",
|
|
36
|
+
css: popupMenuCss,
|
|
37
|
+
window: targetWindow
|
|
38
|
+
});
|
|
39
|
+
const id = useId(idProp);
|
|
40
|
+
const { ariaAttributes, buttonProps, menuOpen, rootRef } = usePopupMenu({
|
|
41
|
+
anchorElement,
|
|
42
|
+
"aria-label": ariaLabel,
|
|
43
|
+
id,
|
|
44
|
+
menuActionHandler,
|
|
45
|
+
menuBuilder,
|
|
46
|
+
menuClassName,
|
|
47
|
+
menuLocation,
|
|
48
|
+
onMenuClose,
|
|
49
|
+
onMenuOpen,
|
|
50
|
+
menuOptions,
|
|
51
|
+
popupPlacement,
|
|
52
|
+
tabIndex
|
|
53
|
+
});
|
|
54
|
+
if (label) {
|
|
55
|
+
return /* @__PURE__ */ jsxs(
|
|
56
|
+
Button,
|
|
57
|
+
{
|
|
58
|
+
...htmlAttributes,
|
|
59
|
+
...ariaAttributes,
|
|
60
|
+
...buttonProps,
|
|
61
|
+
appearance,
|
|
62
|
+
className: cx(classBase, className, `${classBase}-withCaption`, {
|
|
63
|
+
"saltButton-active": menuOpen
|
|
64
|
+
}),
|
|
65
|
+
disabled,
|
|
66
|
+
ref: rootRef,
|
|
67
|
+
sentiment,
|
|
68
|
+
children: [
|
|
69
|
+
icon ? /* @__PURE__ */ jsx(Icon, { name: icon }) : null,
|
|
70
|
+
label
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
} else if (icon) {
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
IconButton,
|
|
77
|
+
{
|
|
78
|
+
...htmlAttributes,
|
|
79
|
+
...ariaAttributes,
|
|
80
|
+
...buttonProps,
|
|
81
|
+
appearance,
|
|
82
|
+
className: cx(classBase, className, {
|
|
83
|
+
"saltButton-active": menuOpen
|
|
84
|
+
}),
|
|
85
|
+
disabled,
|
|
86
|
+
icon,
|
|
87
|
+
ref: rootRef,
|
|
88
|
+
sentiment
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
} else {
|
|
92
|
+
console.error("PopupMenu must have a label or an icon (or both)");
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { PopupMenu };
|
|
98
|
+
//# sourceMappingURL=PopupMenu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PopupMenu.js","sources":["../../../../packages/vuu-popups/src/popup-menu/PopupMenu.tsx"],"sourcesContent":["import { Button, ButtonProps } from \"@salt-ds/core\";\nimport { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport { MenuActionHandler, MenuBuilder } from \"@vuu-ui/vuu-context-menu\";\nimport { Icon, IconButton } from \"@vuu-ui/vuu-ui-controls\";\nimport { useId } from \"@vuu-ui/vuu-utils\";\nimport cx from \"clsx\";\nimport { HTMLAttributes, KeyboardEvent, RefObject } from \"react\";\nimport { PopupPlacement } from \"../popup/Popup\";\nimport { usePopupMenu } from \"./usePopupMenu\";\n\nimport popupMenuCss from \"./PopupMenu.css\";\n\nconst classBase = \"vuuPopupMenu\";\n\nexport type TabAwayClosePopup = {\n closedBy?: string;\n type: \"tab-away\";\n event: KeyboardEvent;\n};\n\nexport type ClickAwayClosePopup = {\n closedBy?: string;\n type: \"click-away\";\n mouseEvt: MouseEvent;\n};\n\nexport type EscapeClosePopup = {\n closedBy?: string;\n event: KeyboardEvent;\n type: \"escape\";\n};\n\nexport type MenuActionClosePopup = {\n closedBy?: string;\n menuId: string;\n options: unknown;\n type: \"menu-action\";\n};\n\nexport type PopupCloseReason =\n | ClickAwayClosePopup\n | EscapeClosePopup\n | MenuActionClosePopup\n | TabAwayClosePopup;\n\nexport type MenuCloseHandler = (reason?: PopupCloseReason) => void;\n\nexport interface PopupMenuProps\n extends Pick<ButtonProps, \"appearance\" | \"sentiment\">,\n HTMLAttributes<HTMLButtonElement> {\n anchorElement?: RefObject<HTMLElement | null>;\n disabled?: boolean;\n icon?: string;\n label?: string;\n menuActionHandler?: MenuActionHandler;\n menuBuilder?: MenuBuilder;\n menuClassName?: string;\n menuLocation?: string;\n menuOptions?: { [key: string]: unknown };\n onMenuClose?: MenuCloseHandler;\n onMenuOpen?: () => void;\n popupPlacement?: PopupPlacement;\n}\n\nexport const PopupMenu = ({\n anchorElement,\n \"aria-label\": ariaLabel,\n appearance = \"transparent\",\n className,\n disabled = false,\n label,\n icon = label ? \"chevron-down\" : \"more-vert\",\n id: idProp,\n menuActionHandler,\n menuBuilder,\n menuClassName,\n menuLocation = \"header\",\n menuOptions,\n onMenuClose,\n onMenuOpen,\n popupPlacement = \"below-right\",\n sentiment = \"neutral\",\n tabIndex = 0,\n ...htmlAttributes\n}: PopupMenuProps) => {\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"vuu-popup-menu\",\n css: popupMenuCss,\n window: targetWindow,\n });\n\n const id = useId(idProp);\n\n const { ariaAttributes, buttonProps, menuOpen, rootRef } = usePopupMenu({\n anchorElement,\n \"aria-label\": ariaLabel,\n id,\n menuActionHandler,\n menuBuilder,\n menuClassName,\n menuLocation,\n onMenuClose,\n onMenuOpen,\n menuOptions,\n popupPlacement,\n tabIndex,\n });\n\n if (label) {\n return (\n <Button\n {...htmlAttributes}\n {...ariaAttributes}\n {...buttonProps}\n appearance={appearance}\n className={cx(classBase, className, `${classBase}-withCaption`, {\n \"saltButton-active\": menuOpen,\n })}\n disabled={disabled}\n ref={rootRef}\n sentiment={sentiment}\n >\n {icon ? <Icon name={icon} /> : null}\n {label}\n </Button>\n );\n } else if (icon) {\n return (\n <IconButton\n {...htmlAttributes}\n {...ariaAttributes}\n {...buttonProps}\n appearance={appearance}\n className={cx(classBase, className, {\n \"saltButton-active\": menuOpen,\n })}\n disabled={disabled}\n icon={icon}\n ref={rootRef}\n sentiment={sentiment}\n />\n );\n } else {\n console.error(\"PopupMenu must have a label or an icon (or both)\");\n return null;\n }\n};\n"],"names":[],"mappings":";;;;;;;;;;AAaA,MAAM,SAAY,GAAA,cAAA;AAoDX,MAAM,YAAY,CAAC;AAAA,EACxB,aAAA;AAAA,EACA,YAAc,EAAA,SAAA;AAAA,EACd,UAAa,GAAA,aAAA;AAAA,EACb,SAAA;AAAA,EACA,QAAW,GAAA,KAAA;AAAA,EACX,KAAA;AAAA,EACA,IAAA,GAAO,QAAQ,cAAiB,GAAA,WAAA;AAAA,EAChC,EAAI,EAAA,MAAA;AAAA,EACJ,iBAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,YAAe,GAAA,QAAA;AAAA,EACf,WAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAiB,GAAA,aAAA;AAAA,EACjB,SAAY,GAAA,SAAA;AAAA,EACZ,QAAW,GAAA,CAAA;AAAA,EACX,GAAG;AACL,CAAsB,KAAA;AACpB,EAAA,MAAM,eAAe,SAAU,EAAA;AAC/B,EAAyB,wBAAA,CAAA;AAAA,IACvB,MAAQ,EAAA,gBAAA;AAAA,IACR,GAAK,EAAA,YAAA;AAAA,IACL,MAAQ,EAAA;AAAA,GACT,CAAA;AAED,EAAM,MAAA,EAAA,GAAK,MAAM,MAAM,CAAA;AAEvB,EAAA,MAAM,EAAE,cAAgB,EAAA,WAAA,EAAa,QAAU,EAAA,OAAA,KAAY,YAAa,CAAA;AAAA,IACtE,aAAA;AAAA,IACA,YAAc,EAAA,SAAA;AAAA,IACd,EAAA;AAAA,IACA,iBAAA;AAAA,IACA,WAAA;AAAA,IACA,aAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA,WAAA;AAAA,IACA,cAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,KAAO,EAAA;AACT,IACE,uBAAA,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACE,GAAG,cAAA;AAAA,QACH,GAAG,cAAA;AAAA,QACH,GAAG,WAAA;AAAA,QACJ,UAAA;AAAA,QACA,WAAW,EAAG,CAAA,SAAA,EAAW,SAAW,EAAA,CAAA,EAAG,SAAS,CAAgB,YAAA,CAAA,EAAA;AAAA,UAC9D,mBAAqB,EAAA;AAAA,SACtB,CAAA;AAAA,QACD,QAAA;AAAA,QACA,GAAK,EAAA,OAAA;AAAA,QACL,SAAA;AAAA,QAEC,QAAA,EAAA;AAAA,UAAA,IAAA,mBAAQ,GAAA,CAAA,IAAA,EAAA,EAAK,IAAM,EAAA,IAAA,EAAM,CAAK,GAAA,IAAA;AAAA,UAC9B;AAAA;AAAA;AAAA,KACH;AAAA,aAEO,IAAM,EAAA;AACf,IACE,uBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACE,GAAG,cAAA;AAAA,QACH,GAAG,cAAA;AAAA,QACH,GAAG,WAAA;AAAA,QACJ,UAAA;AAAA,QACA,SAAA,EAAW,EAAG,CAAA,SAAA,EAAW,SAAW,EAAA;AAAA,UAClC,mBAAqB,EAAA;AAAA,SACtB,CAAA;AAAA,QACD,QAAA;AAAA,QACA,IAAA;AAAA,QACA,GAAK,EAAA,OAAA;AAAA,QACL;AAAA;AAAA,KACF;AAAA,GAEG,MAAA;AACL,IAAA,OAAA,CAAQ,MAAM,kDAAkD,CAAA;AAChE,IAAO,OAAA,IAAA;AAAA;AAEX;;;;"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useState, useRef, useCallback } from 'react';
|
|
2
|
+
import { getPositionRelativeToAnchor } from '../popup/getPositionRelativeToAnchor.js';
|
|
3
|
+
import { useContextMenu } from '@vuu-ui/vuu-context-menu';
|
|
4
|
+
|
|
5
|
+
const usePopupMenu = ({
|
|
6
|
+
anchorElement,
|
|
7
|
+
"aria-label": ariaLabel = "Popup menu",
|
|
8
|
+
id,
|
|
9
|
+
menuActionHandler,
|
|
10
|
+
menuBuilder,
|
|
11
|
+
menuLocation,
|
|
12
|
+
menuOptions,
|
|
13
|
+
onMenuClose,
|
|
14
|
+
onMenuOpen,
|
|
15
|
+
popupPlacement,
|
|
16
|
+
tabIndex
|
|
17
|
+
}) => {
|
|
18
|
+
const [menuOpen, _setMenuOpen] = useState(false);
|
|
19
|
+
const suppressShowMenuRef = useRef(false);
|
|
20
|
+
const rootRef = useRef(null);
|
|
21
|
+
const setMenuOpen = useCallback(
|
|
22
|
+
(isOpen) => {
|
|
23
|
+
_setMenuOpen(isOpen);
|
|
24
|
+
if (isOpen) {
|
|
25
|
+
onMenuOpen?.();
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
const firstOption = document.activeElement?.querySelector(
|
|
28
|
+
".saltMenuItem"
|
|
29
|
+
);
|
|
30
|
+
firstOption?.focus();
|
|
31
|
+
}, 40);
|
|
32
|
+
} else {
|
|
33
|
+
suppressShowMenuRef.current = false;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
[onMenuOpen]
|
|
37
|
+
);
|
|
38
|
+
const showContextMenu = useContextMenu(menuBuilder, menuActionHandler);
|
|
39
|
+
const handleMenuOpenChange = useCallback(
|
|
40
|
+
(isOpen) => {
|
|
41
|
+
if (!isOpen) {
|
|
42
|
+
setMenuOpen(false);
|
|
43
|
+
onMenuClose?.();
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
[onMenuClose, setMenuOpen]
|
|
47
|
+
);
|
|
48
|
+
const showMenu = useCallback(
|
|
49
|
+
(e) => {
|
|
50
|
+
if (suppressShowMenuRef.current) {
|
|
51
|
+
suppressShowMenuRef.current = false;
|
|
52
|
+
} else {
|
|
53
|
+
suppressShowMenuRef.current = true;
|
|
54
|
+
const anchorEl = anchorElement?.current ?? rootRef.current;
|
|
55
|
+
if (anchorEl) {
|
|
56
|
+
const {
|
|
57
|
+
left: x,
|
|
58
|
+
top: y
|
|
59
|
+
// width,
|
|
60
|
+
} = getPositionRelativeToAnchor(anchorEl, popupPlacement, 0, 0);
|
|
61
|
+
setMenuOpen(true);
|
|
62
|
+
showContextMenu(e, menuLocation, menuOptions, {
|
|
63
|
+
// className: menuClassName,
|
|
64
|
+
// id: `${id}-menu`,
|
|
65
|
+
onOpenChange: handleMenuOpenChange,
|
|
66
|
+
x,
|
|
67
|
+
y
|
|
68
|
+
// style: { width: width ? width - 2 : undefined },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
[
|
|
74
|
+
anchorElement,
|
|
75
|
+
popupPlacement,
|
|
76
|
+
setMenuOpen,
|
|
77
|
+
showContextMenu,
|
|
78
|
+
menuLocation,
|
|
79
|
+
menuOptions,
|
|
80
|
+
handleMenuOpenChange
|
|
81
|
+
]
|
|
82
|
+
);
|
|
83
|
+
const ariaAttributes = {
|
|
84
|
+
"aria-controls": menuOpen ? `${id}-menu` : void 0,
|
|
85
|
+
"aria-label": ariaLabel,
|
|
86
|
+
"aria-expanded": menuOpen,
|
|
87
|
+
"aria-haspopup": "menu"
|
|
88
|
+
};
|
|
89
|
+
const buttonProps = {
|
|
90
|
+
id,
|
|
91
|
+
onClick: showMenu,
|
|
92
|
+
tabIndex
|
|
93
|
+
};
|
|
94
|
+
return { ariaAttributes, buttonProps, menuOpen, rootRef };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { usePopupMenu };
|
|
98
|
+
//# sourceMappingURL=usePopupMenu.js.map
|