@vuu-ui/vuu-popups 2.1.18 → 2.1.19-beta.2

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.
Files changed (74) hide show
  1. package/package.json +10 -13
  2. package/src/index.js +4 -0
  3. package/src/popup/Popup.js +29 -0
  4. package/src/popup/getPositionRelativeToAnchor.js +47 -0
  5. package/src/popup/index.js +2 -0
  6. package/src/popup/useAnchoredPosition.js +41 -0
  7. package/src/popup-menu/PopupMenu.css.js +11 -0
  8. package/src/popup-menu/PopupMenu.js +67 -0
  9. package/src/popup-menu/index.js +1 -0
  10. package/src/popup-menu/usePopupMenu.js +72 -0
  11. package/src/portal/Portal.css.js +27 -0
  12. package/src/portal/Portal.js +62 -0
  13. package/src/portal/index.js +1 -0
  14. package/src/tooltip/Tooltip.css.js +76 -0
  15. package/src/tooltip/Tooltip.js +41 -0
  16. package/src/tooltip/index.js +2 -0
  17. package/src/tooltip/useTooltip.js +91 -0
  18. package/src/tooltip/useTooltipAnchoredPosition.js +102 -0
  19. package/cjs/index.js +0 -18
  20. package/cjs/index.js.map +0 -1
  21. package/cjs/popup/Popup.css.js +0 -6
  22. package/cjs/popup/Popup.css.js.map +0 -1
  23. package/cjs/popup/Popup.js +0 -38
  24. package/cjs/popup/Popup.js.map +0 -1
  25. package/cjs/popup/getPositionRelativeToAnchor.js +0 -43
  26. package/cjs/popup/getPositionRelativeToAnchor.js.map +0 -1
  27. package/cjs/popup/useAnchoredPosition.js +0 -58
  28. package/cjs/popup/useAnchoredPosition.js.map +0 -1
  29. package/cjs/popup-menu/PopupMenu.css.js +0 -6
  30. package/cjs/popup-menu/PopupMenu.css.js.map +0 -1
  31. package/cjs/popup-menu/PopupMenu.js +0 -100
  32. package/cjs/popup-menu/PopupMenu.js.map +0 -1
  33. package/cjs/popup-menu/usePopupMenu.js +0 -100
  34. package/cjs/popup-menu/usePopupMenu.js.map +0 -1
  35. package/cjs/portal/Portal.css.js +0 -6
  36. package/cjs/portal/Portal.css.js.map +0 -1
  37. package/cjs/portal/Portal.js +0 -73
  38. package/cjs/portal/Portal.js.map +0 -1
  39. package/cjs/tooltip/Tooltip.css.js +0 -6
  40. package/cjs/tooltip/Tooltip.css.js.map +0 -1
  41. package/cjs/tooltip/Tooltip.js +0 -56
  42. package/cjs/tooltip/Tooltip.js.map +0 -1
  43. package/cjs/tooltip/useTooltip.js +0 -106
  44. package/cjs/tooltip/useTooltip.js.map +0 -1
  45. package/cjs/tooltip/useTooltipAnchoredPosition.js +0 -117
  46. package/cjs/tooltip/useTooltipAnchoredPosition.js.map +0 -1
  47. package/esm/index.js +0 -7
  48. package/esm/index.js.map +0 -1
  49. package/esm/popup/Popup.css.js +0 -4
  50. package/esm/popup/Popup.css.js.map +0 -1
  51. package/esm/popup/Popup.js +0 -36
  52. package/esm/popup/Popup.js.map +0 -1
  53. package/esm/popup/getPositionRelativeToAnchor.js +0 -41
  54. package/esm/popup/getPositionRelativeToAnchor.js.map +0 -1
  55. package/esm/popup/useAnchoredPosition.js +0 -56
  56. package/esm/popup/useAnchoredPosition.js.map +0 -1
  57. package/esm/popup-menu/PopupMenu.css.js +0 -4
  58. package/esm/popup-menu/PopupMenu.css.js.map +0 -1
  59. package/esm/popup-menu/PopupMenu.js +0 -98
  60. package/esm/popup-menu/PopupMenu.js.map +0 -1
  61. package/esm/popup-menu/usePopupMenu.js +0 -98
  62. package/esm/popup-menu/usePopupMenu.js.map +0 -1
  63. package/esm/portal/Portal.css.js +0 -4
  64. package/esm/portal/Portal.css.js.map +0 -1
  65. package/esm/portal/Portal.js +0 -71
  66. package/esm/portal/Portal.js.map +0 -1
  67. package/esm/tooltip/Tooltip.css.js +0 -4
  68. package/esm/tooltip/Tooltip.css.js.map +0 -1
  69. package/esm/tooltip/Tooltip.js +0 -54
  70. package/esm/tooltip/Tooltip.js.map +0 -1
  71. package/esm/tooltip/useTooltip.js +0 -104
  72. package/esm/tooltip/useTooltip.js.map +0 -1
  73. package/esm/tooltip/useTooltipAnchoredPosition.js +0 -115
  74. package/esm/tooltip/useTooltipAnchoredPosition.js.map +0 -1
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
- "version": "2.1.18",
2
+ "version": "2.1.19-beta.2",
3
3
  "description": "VUU popup components - Context Menu, Dialog etc",
4
4
  "author": "heswell",
5
5
  "license": "Apache-2.0",
6
+ "type": "module",
6
7
  "dependencies": {
7
8
  "@salt-ds/core": "1.54.1",
8
9
  "@salt-ds/styles": "0.2.1",
9
10
  "@salt-ds/window": "0.1.1",
10
- "@vuu-ui/vuu-context-menu": "2.1.18",
11
- "@vuu-ui/vuu-data-types": "2.1.18",
12
- "@vuu-ui/vuu-layout": "2.1.18",
13
- "@vuu-ui/vuu-utils": "2.1.18",
14
- "@vuu-ui/vuu-ui-controls": "2.1.18"
11
+ "@vuu-ui/vuu-context-menu": "2.1.19-beta.2",
12
+ "@vuu-ui/vuu-data-types": "2.1.19-beta.2",
13
+ "@vuu-ui/vuu-layout": "2.1.19-beta.2",
14
+ "@vuu-ui/vuu-utils": "2.1.19-beta.2",
15
+ "@vuu-ui/vuu-ui-controls": "2.1.19-beta.2"
15
16
  },
16
17
  "peerDependencies": {
17
18
  "clsx": "^2.0.0",
@@ -21,20 +22,16 @@
21
22
  "sideEffects": false,
22
23
  "files": [
23
24
  "README.md",
24
- "esm",
25
- "cjs",
25
+ "src",
26
26
  "/types"
27
27
  ],
28
28
  "exports": {
29
29
  ".": {
30
- "require": "./cjs/index.js",
31
- "import": "./esm/index.js",
30
+ "import": "./src/index.js",
32
31
  "types": "./types/index.d.ts"
33
32
  }
34
33
  },
35
- "main": "cjs/index.js",
36
- "module": "esm/index.js",
34
+ "module": "index.js",
37
35
  "name": "@vuu-ui/vuu-popups",
38
- "type": "module",
39
36
  "types": "types/index.d.ts"
40
37
  }
package/src/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./popup/index.js";
2
+ export * from "./popup-menu/index.js";
3
+ export * from "./portal/index.js";
4
+ export * from "./tooltip/index.js";
@@ -0,0 +1,29 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { useComponentCssInjection } from "@salt-ds/styles";
4
+ import { useWindow } from "@salt-ds/window";
5
+ import { useAnchoredPosition } from "./useAnchoredPosition.js";
6
+ import Popup from "./Popup.css";
7
+ const PopupComponent = ({ children, className, anchorElement, minWidth, offsetLeft, offsetTop, placement, position: positionProp })=>{
8
+ const targetWindow = useWindow();
9
+ useComponentCssInjection({
10
+ testId: "vuu-popup",
11
+ css: Popup,
12
+ window: targetWindow
13
+ });
14
+ const { popupRef, position } = useAnchoredPosition({
15
+ anchorElement,
16
+ minWidth,
17
+ offsetLeft,
18
+ offsetTop,
19
+ placement,
20
+ position: positionProp
21
+ });
22
+ return void 0 === position ? null : /*#__PURE__*/ jsx("div", {
23
+ className: clsx("vuuPortal", className),
24
+ ref: popupRef,
25
+ style: position,
26
+ children: children
27
+ });
28
+ };
29
+ export { PopupComponent };
@@ -0,0 +1,47 @@
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 {
6
+ left: left + offsetLeft,
7
+ top: bottom + offsetTop
8
+ };
9
+ case "right":
10
+ return {
11
+ left: right + offsetLeft,
12
+ top: top + offsetTop
13
+ };
14
+ case "below-center":
15
+ return {
16
+ left: left + width / 2 + offsetLeft,
17
+ top: bottom + offsetTop
18
+ };
19
+ case "below-right":
20
+ return {
21
+ left: left,
22
+ minWidth,
23
+ top: bottom + offsetTop
24
+ };
25
+ case "below-full-width":
26
+ return {
27
+ left: left + offsetLeft,
28
+ minWidth,
29
+ top: bottom + offsetTop,
30
+ width
31
+ };
32
+ case "center":
33
+ if (dimensions) return {
34
+ left: width / 2 - dimensions.width / 2 + offsetLeft,
35
+ top: height / 2 - dimensions.height / 2 + offsetTop,
36
+ visibility: "visible"
37
+ };
38
+ return {
39
+ left: width / 2 + offsetLeft,
40
+ top: height / 2 + offsetTop,
41
+ visibility: "hidden"
42
+ };
43
+ default:
44
+ throw Error(`Popup getPositionRelativeToAnchor non-supported placement value ${placement}`);
45
+ }
46
+ };
47
+ export { getPositionRelativeToAnchor };
@@ -0,0 +1,2 @@
1
+ export * from "./Popup.js";
2
+ export * from "./useAnchoredPosition.js";
@@ -0,0 +1,41 @@
1
+ import { useCallback, useLayoutEffect, useRef, useState } from "react";
2
+ import { getPositionRelativeToAnchor } from "./getPositionRelativeToAnchor.js";
3
+ const useAnchoredPosition = ({ anchorElement, minWidth, offsetLeft = 0, offsetTop = 0, placement, position: positionProp })=>{
4
+ const popupRef = useRef(null);
5
+ const [position, setPosition] = useState(positionProp);
6
+ useLayoutEffect(()=>{
7
+ if ("absolute" === placement && positionProp) setPosition(positionProp);
8
+ else if (anchorElement.current && "auto" !== placement) {
9
+ const dimensions = null === popupRef.current ? void 0 : popupRef.current.getBoundingClientRect();
10
+ const position = getPositionRelativeToAnchor(anchorElement.current, placement, offsetLeft, offsetTop, minWidth, dimensions);
11
+ setPosition(position);
12
+ }
13
+ }, [
14
+ anchorElement,
15
+ minWidth,
16
+ offsetLeft,
17
+ offsetTop,
18
+ placement,
19
+ positionProp
20
+ ]);
21
+ const popupCallbackRef = useCallback((el)=>{
22
+ popupRef.current = el;
23
+ if (el && "center" === placement && anchorElement.current) {
24
+ const { height, width } = el.getBoundingClientRect();
25
+ setPosition(getPositionRelativeToAnchor(anchorElement.current, placement, offsetLeft, offsetTop, void 0, {
26
+ height,
27
+ width
28
+ }));
29
+ }
30
+ }, [
31
+ anchorElement,
32
+ offsetLeft,
33
+ offsetTop,
34
+ placement
35
+ ]);
36
+ return {
37
+ position,
38
+ popupRef: "center" === placement ? popupCallbackRef : void 0
39
+ };
40
+ };
41
+ export { useAnchoredPosition };
@@ -0,0 +1,11 @@
1
+ const css = `
2
+ .vuuPopupMenu {
3
+ --vuu-icon-height: 20px;
4
+ --vuu-icon-left: 2px;
5
+ --vuu-icon-size: var(--vuuPopupMenu-iconSize, 16px);
6
+ --vuu-icon-width: 16px;
7
+ }
8
+
9
+
10
+ `;
11
+ export default css;
@@ -0,0 +1,67 @@
1
+ import { jsx, jsxs } 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 clsx from "clsx";
8
+ import { usePopupMenu } from "./usePopupMenu.js";
9
+ import PopupMenu from "./PopupMenu.css";
10
+ const classBase = "vuuPopupMenu";
11
+ const PopupMenu_PopupMenu = ({ anchorElement, "aria-label": ariaLabel, appearance = "transparent", className, disabled = false, label, icon = label ? "chevron-down" : "more-vert", id: idProp, menuActionHandler, menuBuilder, menuClassName, menuLocation = "header", menuOptions, onMenuClose, onMenuOpen, popupPlacement = "below-right", sentiment = "neutral", tabIndex = 0, ...htmlAttributes })=>{
12
+ const targetWindow = useWindow();
13
+ useComponentCssInjection({
14
+ testId: "vuu-popup-menu",
15
+ css: PopupMenu,
16
+ window: targetWindow
17
+ });
18
+ const id = useId(idProp);
19
+ const { ariaAttributes, buttonProps, menuOpen, rootRef } = usePopupMenu({
20
+ anchorElement,
21
+ "aria-label": ariaLabel,
22
+ id,
23
+ menuActionHandler,
24
+ menuBuilder,
25
+ menuClassName,
26
+ menuLocation,
27
+ onMenuClose,
28
+ onMenuOpen,
29
+ menuOptions,
30
+ popupPlacement,
31
+ tabIndex
32
+ });
33
+ if (label) return /*#__PURE__*/ jsxs(Button, {
34
+ ...htmlAttributes,
35
+ ...ariaAttributes,
36
+ ...buttonProps,
37
+ appearance: appearance,
38
+ className: clsx(classBase, className, `${classBase}-withCaption`, {
39
+ "saltButton-active": menuOpen
40
+ }),
41
+ disabled: disabled,
42
+ ref: rootRef,
43
+ sentiment: sentiment,
44
+ children: [
45
+ icon ? /*#__PURE__*/ jsx(Icon, {
46
+ name: icon
47
+ }) : null,
48
+ label
49
+ ]
50
+ });
51
+ if (icon) return /*#__PURE__*/ jsx(IconButton, {
52
+ ...htmlAttributes,
53
+ ...ariaAttributes,
54
+ ...buttonProps,
55
+ appearance: appearance,
56
+ className: clsx(classBase, className, {
57
+ "saltButton-active": menuOpen
58
+ }),
59
+ disabled: disabled,
60
+ icon: icon,
61
+ ref: rootRef,
62
+ sentiment: sentiment
63
+ });
64
+ console.error("PopupMenu must have a label or an icon (or both)");
65
+ return null;
66
+ };
67
+ export { PopupMenu_PopupMenu as PopupMenu };
@@ -0,0 +1 @@
1
+ export * from "./PopupMenu.js";
@@ -0,0 +1,72 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ import { getPositionRelativeToAnchor } from "../popup/getPositionRelativeToAnchor.js";
3
+ import { useContextMenu } from "@vuu-ui/vuu-context-menu";
4
+ const usePopupMenu = ({ anchorElement, "aria-label": ariaLabel = "Popup menu", id, menuActionHandler, menuBuilder, menuLocation, menuOptions, onMenuClose, onMenuOpen, popupPlacement, tabIndex })=>{
5
+ const [menuOpen, _setMenuOpen] = useState(false);
6
+ const suppressShowMenuRef = useRef(false);
7
+ const rootRef = useRef(null);
8
+ const setMenuOpen = useCallback((isOpen)=>{
9
+ _setMenuOpen(isOpen);
10
+ if (isOpen) {
11
+ onMenuOpen?.();
12
+ setTimeout(()=>{
13
+ const firstOption = document.activeElement?.querySelector(".saltMenuItem");
14
+ firstOption?.focus();
15
+ }, 40);
16
+ } else suppressShowMenuRef.current = false;
17
+ }, [
18
+ onMenuOpen
19
+ ]);
20
+ const showContextMenu = useContextMenu(menuBuilder, menuActionHandler);
21
+ const handleMenuOpenChange = useCallback((isOpen)=>{
22
+ if (!isOpen) {
23
+ setMenuOpen(false);
24
+ onMenuClose?.();
25
+ }
26
+ }, [
27
+ onMenuClose,
28
+ setMenuOpen
29
+ ]);
30
+ const showMenu = useCallback((e)=>{
31
+ if (suppressShowMenuRef.current) suppressShowMenuRef.current = false;
32
+ else {
33
+ suppressShowMenuRef.current = true;
34
+ const anchorEl = anchorElement?.current ?? rootRef.current;
35
+ if (anchorEl) {
36
+ const { left: x, top: y } = getPositionRelativeToAnchor(anchorEl, popupPlacement, 0, 0);
37
+ setMenuOpen(true);
38
+ showContextMenu(e, menuLocation, menuOptions, {
39
+ onOpenChange: handleMenuOpenChange,
40
+ x,
41
+ y
42
+ });
43
+ }
44
+ }
45
+ }, [
46
+ anchorElement,
47
+ popupPlacement,
48
+ setMenuOpen,
49
+ showContextMenu,
50
+ menuLocation,
51
+ menuOptions,
52
+ handleMenuOpenChange
53
+ ]);
54
+ const ariaAttributes = {
55
+ "aria-controls": menuOpen ? `${id}-menu` : void 0,
56
+ "aria-label": ariaLabel,
57
+ "aria-expanded": menuOpen,
58
+ "aria-haspopup": "menu"
59
+ };
60
+ const buttonProps = {
61
+ id,
62
+ onClick: showMenu,
63
+ tabIndex
64
+ };
65
+ return {
66
+ ariaAttributes,
67
+ buttonProps,
68
+ menuOpen,
69
+ rootRef
70
+ };
71
+ };
72
+ export { usePopupMenu };
@@ -0,0 +1,27 @@
1
+ const css = `
2
+ .vuuPortal {
3
+ z-index: 100;
4
+ position: absolute;
5
+ }
6
+
7
+ .vuuPortal:not(.vuuPopup) {
8
+ background-color: var(--salt-container-primary-background);
9
+ border-color: var(--vuuMenuList-borderColor, var(--salt-container-primary-borderColor));
10
+ border-style: solid;
11
+ border-width: 1px;
12
+ border-radius: 4px;
13
+ padding: 4px 0;
14
+ box-shadow: 0 6px 12px #0000002d;
15
+ }
16
+
17
+ .vuuPortal:has(.vuuDropdown-popup-component.vuuList-empty) {
18
+ display: none;
19
+ }
20
+
21
+ #vuu-portal-root {
22
+ z-index: 1000;
23
+ }
24
+
25
+
26
+ `;
27
+ export default css;
@@ -0,0 +1,62 @@
1
+ import { useThemeAttributes } from "@vuu-ui/vuu-utils";
2
+ import { useLayoutEffect, useRef, useState } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { useComponentCssInjection } from "@salt-ds/styles";
5
+ import { useWindow } from "@salt-ds/window";
6
+ import Portal from "./Portal.css";
7
+ function getContainer(container) {
8
+ return "function" == typeof container ? container() : container;
9
+ }
10
+ const DEFAULT_ID = [
11
+ "vuu-dialog-portal-root",
12
+ "vuu-portal-root"
13
+ ];
14
+ const getFirstAvailableElementById = (id)=>{
15
+ if (!Array.isArray(id)) return document.getElementById(id);
16
+ for (const i of id){
17
+ const element = document.getElementById(i);
18
+ if (element) return element;
19
+ }
20
+ return null;
21
+ };
22
+ const Portal_Portal = ({ children, container: containerProp = document.body, id = DEFAULT_ID, onRender, open = true, themeAttributes })=>{
23
+ const targetWindow = useWindow();
24
+ useComponentCssInjection({
25
+ testId: "vuu-portal",
26
+ css: Portal,
27
+ window: targetWindow
28
+ });
29
+ const [mounted, setMounted] = useState(false);
30
+ const portalRef = useRef(null);
31
+ const container = getContainer(containerProp) ?? document.body;
32
+ const [themeClass, densityClass, dataMode] = useThemeAttributes(themeAttributes);
33
+ useLayoutEffect(()=>{
34
+ const root = getFirstAvailableElementById(id);
35
+ if (root) portalRef.current = root;
36
+ else {
37
+ portalRef.current = document.createElement("div");
38
+ portalRef.current.id = "string" == typeof id ? id : id.length > 0 ? id.at(-1) : "vuu-portal-root";
39
+ }
40
+ const el = portalRef.current;
41
+ if (!container.contains(el)) container.appendChild(el);
42
+ el.classList.add(themeClass, densityClass);
43
+ el.dataset.mode = dataMode;
44
+ setMounted(true);
45
+ }, [
46
+ id,
47
+ container,
48
+ themeClass,
49
+ densityClass,
50
+ dataMode
51
+ ]);
52
+ useLayoutEffect(()=>{
53
+ requestAnimationFrame(()=>{
54
+ onRender?.();
55
+ });
56
+ }, [
57
+ onRender
58
+ ]);
59
+ if (open && mounted && portalRef.current && children) return /*#__PURE__*/ createPortal(children, portalRef.current);
60
+ return null;
61
+ };
62
+ export { Portal_Portal as Portal };
@@ -0,0 +1 @@
1
+ export * from "./Portal.js";
@@ -0,0 +1,76 @@
1
+ const css = `
2
+ .vuuTooltip {
3
+ --tooltip-align: flex-start;
4
+ --tooltip-background: var(--salt-actionable-bold-background-hover);
5
+ --tooltip-border: var(--salt-actionable-bold-background-hover);
6
+ --tooltip-color: var(--salt-actionable-bold-foreground-hover);
7
+ --tooltip-justify: flex-start;
8
+ --tooltip-top: auto;
9
+ --tooltip-right: auto;
10
+ --tooltip-bottom: auto;
11
+ --tooltip-left: auto;
12
+ align-items: var(--tooltip-align);
13
+ justify-content: var(--tooltip-justify);
14
+ z-index: 100;
15
+ transition: opacity .15s ease-in;
16
+ display: flex;
17
+ position: absolute;
18
+
19
+ &.vuuHidden {
20
+ opacity: 0;
21
+ }
22
+ }
23
+
24
+ .vuuTooltip-content {
25
+ background-color: var(--tooltip-background);
26
+ border-color: var(--tooltip-border);
27
+ border-radius: var(--vuuTooltip-borderRadius, 6px);
28
+ color: var(--tooltip-color);
29
+ white-space: nowrap;
30
+ border-style: solid;
31
+ border-width: 1px;
32
+ padding: 2px 8px;
33
+ line-height: 24px;
34
+ box-shadow: 0 4px 4px #00000040;
35
+ }
36
+
37
+ .vuuTooltip:before {
38
+ background-color: var(--tooltip-border);
39
+ content: " ";
40
+ z-index: -1;
41
+ width: 12px;
42
+ height: 12px;
43
+ display: block;
44
+ position: absolute;
45
+ transform: rotate(45deg);
46
+ }
47
+
48
+ .vuuTooltip[data-align="above"]:before {
49
+ bottom: -6px;
50
+ left: calc(50% - 6px);
51
+ }
52
+
53
+ .vuuTooltip[data-align="below"]:before {
54
+ top: -6px;
55
+ left: calc(50% - 6px);
56
+ }
57
+
58
+ .vuuTooltip[data-align="right"]:before {
59
+ top: calc(50% - 6px);
60
+ left: -6px;
61
+ }
62
+
63
+ .vuuTooltip[data-align="left"]:before {
64
+ top: calc(50% - 6px);
65
+ right: -6px;
66
+ }
67
+
68
+ .vuuTooltip-error {
69
+ --tooltip-background: var(--vuu-color-red-50);
70
+ --tooltip-color: white;
71
+ color: #fff;
72
+ }
73
+
74
+
75
+ `;
76
+ export default css;
@@ -0,0 +1,41 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Portal } from "../portal/index.js";
3
+ import { useTooltipAnchoredPosition } from "./useTooltipAnchoredPosition.js";
4
+ import clsx from "clsx";
5
+ import { useComponentCssInjection } from "@salt-ds/styles";
6
+ import { useWindow } from "@salt-ds/window";
7
+ import Tooltip from "./Tooltip.css";
8
+ const classBase = "vuuTooltip";
9
+ const Tooltip_Tooltip = ({ anchorElement, children, className, id, onMouseEnter, onMouseLeave, placement: placementProp, status, style: styleProp })=>{
10
+ const targetWindow = useWindow();
11
+ useComponentCssInjection({
12
+ testId: "vuu-tooltip",
13
+ css: Tooltip,
14
+ window: targetWindow
15
+ });
16
+ const ref = useTooltipAnchoredPosition({
17
+ anchorElement,
18
+ placement: placementProp
19
+ });
20
+ return /*#__PURE__*/ jsx(Portal, {
21
+ children: /*#__PURE__*/ jsx("div", {
22
+ className: clsx(classBase, className, "vuuHidden", {
23
+ [`${classBase}-error`]: "error" === status
24
+ }),
25
+ id: id,
26
+ ref: ref,
27
+ style: {
28
+ ...styleProp,
29
+ left: 0,
30
+ top: 0
31
+ },
32
+ children: /*#__PURE__*/ jsx("span", {
33
+ className: `${classBase}-content`,
34
+ onMouseEnter: onMouseEnter,
35
+ onMouseLeave: onMouseLeave,
36
+ children: children
37
+ })
38
+ })
39
+ });
40
+ };
41
+ export { Tooltip_Tooltip as Tooltip };
@@ -0,0 +1,2 @@
1
+ export * from "./Tooltip.js";
2
+ export * from "./useTooltip.js";
@@ -0,0 +1,91 @@
1
+ import { queryClosest, useId } from "@vuu-ui/vuu-utils";
2
+ import { useCallback, useRef, useState } from "react";
3
+ const useTooltip = ({ anchorQuery = "*", id: idProp, placement = [
4
+ "right",
5
+ "above",
6
+ "below"
7
+ ], tooltipContent })=>{
8
+ const hideTooltipRef = useRef(void 0);
9
+ const isHoveringRef = useRef(false);
10
+ const anchorElementRef = useRef(null);
11
+ const mouseEnterTimerRef = useRef(void 0);
12
+ const mouseLeaveTimerRef = useRef(void 0);
13
+ const [tooltipProps, setTooltipProps] = useState();
14
+ const id = useId(idProp);
15
+ const escapeListener = useCallback((evt)=>{
16
+ if ("Escape" === evt.key) hideTooltipRef.current?.();
17
+ }, []);
18
+ hideTooltipRef.current = useCallback(()=>{
19
+ setTooltipProps(void 0);
20
+ document.removeEventListener("keydown", escapeListener);
21
+ }, [
22
+ escapeListener
23
+ ]);
24
+ const handleMouseEnterTooltip = useCallback(()=>{
25
+ window.clearTimeout(mouseLeaveTimerRef.current);
26
+ }, []);
27
+ const handleMouseLeaveTooltip = useCallback(()=>{
28
+ hideTooltipRef.current?.();
29
+ }, []);
30
+ const hideTooltip = useCallback((defer = 0)=>{
31
+ if (mouseEnterTimerRef.current) {
32
+ window.clearTimeout(mouseEnterTimerRef.current);
33
+ mouseEnterTimerRef.current = void 0;
34
+ } else if (hideTooltipRef.current) if (0 === defer) hideTooltipRef.current();
35
+ else mouseLeaveTimerRef.current = window.setTimeout(hideTooltipRef.current, defer);
36
+ }, []);
37
+ const showTooltip = useCallback((ref = anchorElementRef)=>{
38
+ const { current: anchorEl } = ref;
39
+ if (anchorEl) {
40
+ setTooltipProps({
41
+ anchorElement: ref,
42
+ children: tooltipContent,
43
+ id: `${id}-tooltip`,
44
+ onMouseEnter: handleMouseEnterTooltip,
45
+ onMouseLeave: handleMouseLeaveTooltip,
46
+ placement: placement
47
+ });
48
+ document.addEventListener("keydown", escapeListener);
49
+ }
50
+ mouseEnterTimerRef.current = void 0;
51
+ hideTooltip(isHoveringRef.current ? 3000 : 1000);
52
+ }, [
53
+ escapeListener,
54
+ handleMouseEnterTooltip,
55
+ handleMouseLeaveTooltip,
56
+ hideTooltip,
57
+ id,
58
+ placement,
59
+ tooltipContent
60
+ ]);
61
+ const handleMouseEnter = useCallback((evt)=>{
62
+ isHoveringRef.current = true;
63
+ const el = queryClosest(evt.target, anchorQuery);
64
+ if (el) {
65
+ console.log(`el ${el.classList}`);
66
+ anchorElementRef.current = el;
67
+ mouseEnterTimerRef.current = window.setTimeout(showTooltip, 800);
68
+ }
69
+ }, [
70
+ anchorQuery,
71
+ showTooltip
72
+ ]);
73
+ const handleMouseLeave = useCallback(()=>{
74
+ isHoveringRef.current = false;
75
+ hideTooltip(200);
76
+ }, [
77
+ hideTooltip
78
+ ]);
79
+ const anchorProps = {
80
+ "aria-describedby": `${id}-tooltip`,
81
+ onMouseEnter: handleMouseEnter,
82
+ onMouseLeave: handleMouseLeave
83
+ };
84
+ return {
85
+ anchorProps,
86
+ hideTooltip,
87
+ showTooltip,
88
+ tooltipProps
89
+ };
90
+ };
91
+ export { useTooltip };