@clayui/tooltip 3.142.0 → 3.143.0

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.
@@ -0,0 +1,208 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ */
5
+
6
+ import { ClayPortal, Keys, delegate, useInteractionFocus } from '@clayui/shared';
7
+ import React, { useCallback, useEffect, useReducer, useRef } from 'react';
8
+ import warning from 'warning';
9
+ import { Tooltip } from "./Tooltip.js";
10
+ import { useAlign } from "./useAlign.js";
11
+ import { useClosestTitle } from "./useClosestTitle.js";
12
+ import { useTooltipState } from "./useTooltipState.js";
13
+ const initialState = {
14
+ align: 'top',
15
+ floating: false,
16
+ setAsHTML: false,
17
+ title: ''
18
+ };
19
+ const TRIGGER_HIDE_EVENTS = ['dragstart', 'mouseout', 'mouseup', 'pointerup', 'touchend'];
20
+ const TRIGGER_SHOW_EVENTS = ['mouseover', 'mouseup', 'pointerdown', 'touchstart'];
21
+ const reducer = (state, _ref) => {
22
+ let {
23
+ type,
24
+ ...payload
25
+ } = _ref;
26
+ switch (type) {
27
+ case 'update':
28
+ return {
29
+ ...state,
30
+ ...payload
31
+ };
32
+ case 'reset':
33
+ return {
34
+ ...state,
35
+ align: initialState.align,
36
+ floating: false
37
+ };
38
+ default:
39
+ throw new TypeError();
40
+ }
41
+ };
42
+ export const ClayTooltipProvider = _ref2 => {
43
+ let {
44
+ autoAlign = true,
45
+ children,
46
+ containerProps = {},
47
+ contentRenderer = props => props.title,
48
+ delay = 600,
49
+ scope
50
+ } = _ref2;
51
+ const [{
52
+ align,
53
+ floating,
54
+ setAsHTML,
55
+ title = ''
56
+ }, dispatch] = useReducer(reducer, initialState);
57
+ const tooltipRef = useRef(null);
58
+ const {
59
+ getInteraction,
60
+ isFocusVisible
61
+ } = useInteractionFocus();
62
+ const isHovered = useRef(false);
63
+ const isFocused = useRef(false);
64
+ const {
65
+ close,
66
+ isOpen,
67
+ open
68
+ } = useTooltipState({
69
+ delay
70
+ });
71
+ const {
72
+ forceHide,
73
+ getProps,
74
+ onHide,
75
+ target,
76
+ titleNode
77
+ } = useClosestTitle({
78
+ forceHide: useCallback(() => {
79
+ dispatch({
80
+ type: 'reset'
81
+ });
82
+ close();
83
+ }, []),
84
+ onClick: useCallback(() => {
85
+ isFocused.current = false;
86
+ isHovered.current = false;
87
+ }, []),
88
+ onHide: useCallback(() => {
89
+ if (!isHovered.current && !isFocused.current) {
90
+ dispatch({
91
+ type: 'reset'
92
+ });
93
+ close();
94
+ }
95
+ }, []),
96
+ tooltipRef
97
+ });
98
+ useAlign({
99
+ align,
100
+ autoAlign,
101
+ floating,
102
+ isOpen,
103
+ onAlign: useCallback(align => dispatch({
104
+ align,
105
+ type: 'update'
106
+ }), []),
107
+ sourceElement: tooltipRef,
108
+ targetElement: titleNode,
109
+ title
110
+ });
111
+ const onShow = useCallback(event => {
112
+ if (isHovered.current || isFocused.current) {
113
+ const props = getProps(event, isHovered.current);
114
+ if (props) {
115
+ dispatch({
116
+ align: props.align ?? align,
117
+ floating: props.floating,
118
+ setAsHTML: props.setAsHTML,
119
+ title: props.title,
120
+ type: 'update'
121
+ });
122
+ open(isFocused.current, props.delay ? Number(props.delay) : undefined);
123
+ }
124
+ }
125
+ }, [align]);
126
+ useEffect(() => {
127
+ const handleEsc = event => {
128
+ if (isOpen && event.key === Keys.Esc) {
129
+ event.stopImmediatePropagation();
130
+ forceHide();
131
+ }
132
+ };
133
+ document.addEventListener('keyup', handleEsc, true);
134
+ return () => document.removeEventListener('keyup', handleEsc, true);
135
+ }, [isOpen]);
136
+ const onHoverStart = event => {
137
+ if (getInteraction() === 'pointer') {
138
+ isHovered.current = true;
139
+ } else {
140
+ isHovered.current = false;
141
+ }
142
+ onShow(event);
143
+ };
144
+ const onHoverEnd = event => {
145
+ isFocused.current = false;
146
+ isHovered.current = false;
147
+ onHide(event);
148
+ };
149
+ const onFocus = event => {
150
+ if (isFocusVisible()) {
151
+ isFocused.current = true;
152
+ onShow(event);
153
+ }
154
+ };
155
+ const onBlur = event => {
156
+ isFocused.current = false;
157
+ isHovered.current = false;
158
+ onHide(event);
159
+ };
160
+ useEffect(() => {
161
+ if (scope) {
162
+ const disposeShowEvents = TRIGGER_SHOW_EVENTS.map(eventName => delegate(document.body, eventName, scope, onHoverStart));
163
+ const disposeHideEvents = TRIGGER_HIDE_EVENTS.map(eventName => delegate(document.body, eventName, `${scope}, .tooltip`, onHoverEnd));
164
+ const disposeShowFocus = delegate(document.body, 'focus', `${scope}, .tooltip`, onFocus, true);
165
+ const disposeCloseBlur = delegate(document.body, 'blur', `${scope}, .tooltip`, onBlur, true);
166
+ return () => {
167
+ disposeShowEvents.forEach(_ref3 => {
168
+ let {
169
+ dispose
170
+ } = _ref3;
171
+ return dispose();
172
+ });
173
+ disposeHideEvents.forEach(_ref4 => {
174
+ let {
175
+ dispose
176
+ } = _ref4;
177
+ return dispose();
178
+ });
179
+ disposeShowFocus.dispose();
180
+ disposeCloseBlur.dispose();
181
+ };
182
+ }
183
+ }, [onShow]);
184
+ "production" !== "production" ? warning(typeof children === 'undefined' && typeof scope !== 'undefined' || typeof scope === 'undefined' && typeof children !== 'undefined', '<TooltipProvider />: You must use at least one of the following props: `children` or `scope`.') : void 0;
185
+ "production" !== "production" ? warning(typeof children !== 'undefined' || typeof scope !== 'undefined', '<TooltipProvider />: If you want to use `scope`, use <TooltipProvider /> as a singleton and do not pass `children`.') : void 0;
186
+ "production" !== "production" ? warning(children?.type !== React.Fragment, '<TooltipProvider />: React Fragment is not allowed as a child to TooltipProvider. Child must be a single HTML element that accepts `onMouseOver` and `onMouseOut`.') : void 0;
187
+ const titleContent = contentRenderer({
188
+ targetNode: target.current,
189
+ title
190
+ });
191
+ const tooltip = isOpen && /*#__PURE__*/React.createElement(ClayPortal, containerProps, /*#__PURE__*/React.createElement(Tooltip, {
192
+ alignPosition: align,
193
+ ref: tooltipRef,
194
+ show: true
195
+ }, setAsHTML && typeof titleContent === 'string' ? /*#__PURE__*/React.createElement("span", {
196
+ dangerouslySetInnerHTML: {
197
+ __html: titleContent
198
+ }
199
+ }) : titleContent));
200
+ return /*#__PURE__*/React.createElement(React.Fragment, null, scope ? /*#__PURE__*/React.createElement(React.Fragment, null, tooltip, children) : children && /*#__PURE__*/React.cloneElement(children, {
201
+ ...children.props,
202
+ children: /*#__PURE__*/React.createElement(React.Fragment, null, children.props.children, tooltip),
203
+ onBlur,
204
+ onFocus,
205
+ onMouseOut: onHoverEnd,
206
+ onMouseOver: onHoverStart
207
+ }));
208
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ */
5
+ import { Tooltip } from "./Tooltip.js";
6
+ import { ClayTooltipProvider } from "./TooltipProvider.js";
7
+ export { Tooltip, ClayTooltipProvider };
8
+ export default Tooltip;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ */
5
+
6
+ import { doAlign, useMousePosition } from '@clayui/shared';
7
+ import { alignPoint } from 'dom-align';
8
+ import { useEffect } from 'react';
9
+ const ALIGNMENTS = ['top', 'top-right', 'right', 'bottom-right', 'bottom', 'bottom-left', 'left', 'top-left'];
10
+ const ALIGNMENTS_MAP = {
11
+ bottom: ['tc', 'bc'],
12
+ 'bottom-left': ['tl', 'bl'],
13
+ 'bottom-right': ['tr', 'br'],
14
+ left: ['cr', 'cl'],
15
+ right: ['cl', 'cr'],
16
+ top: ['bc', 'tc'],
17
+ 'top-left': ['bl', 'tl'],
18
+ 'top-right': ['br', 'tr']
19
+ };
20
+ const ALIGNMENTS_INVERSE_MAP = {
21
+ bctc: 'top',
22
+ bltl: 'top-left',
23
+ brtr: 'top-right',
24
+ clcr: 'right',
25
+ crcl: 'left',
26
+ tcbc: 'bottom',
27
+ tlbl: 'bottom-left',
28
+ trbr: 'bottom-right'
29
+ };
30
+ const BOTTOM_OFFSET = [0, 7];
31
+ const LEFT_OFFSET = [-7, 0];
32
+ const RIGHT_OFFSET = [7, 0];
33
+ const TOP_OFFSET = [0, -7];
34
+ const OFFSET_MAP = {
35
+ bctc: TOP_OFFSET,
36
+ bltl: TOP_OFFSET,
37
+ brtr: TOP_OFFSET,
38
+ clcr: RIGHT_OFFSET,
39
+ crcl: LEFT_OFFSET,
40
+ tcbc: BOTTOM_OFFSET,
41
+ tlbl: BOTTOM_OFFSET,
42
+ trbr: BOTTOM_OFFSET
43
+ };
44
+ const ALIGNMENTS_FORCE_MAP = {
45
+ ...ALIGNMENTS_INVERSE_MAP,
46
+ bctc: 'top-left',
47
+ tcbc: 'bottom-left'
48
+ };
49
+ export function useAlign(_ref) {
50
+ let {
51
+ align,
52
+ autoAlign,
53
+ floating,
54
+ isOpen,
55
+ onAlign,
56
+ sourceElement,
57
+ targetElement,
58
+ title
59
+ } = _ref;
60
+ const mousePosition = useMousePosition(20);
61
+ useEffect(() => {
62
+ if (sourceElement.current && isOpen && floating) {
63
+ const points = ALIGNMENTS_MAP[align || 'top'];
64
+ const [clientX, clientY] = mousePosition;
65
+ alignPoint(sourceElement.current, {
66
+ clientX,
67
+ clientY
68
+ }, {
69
+ offset: OFFSET_MAP[points.join('')],
70
+ points
71
+ });
72
+ }
73
+ }, [isOpen, floating]);
74
+ useEffect(() => {
75
+ if (targetElement.current && sourceElement.current && isOpen && !floating) {
76
+ const points = ALIGNMENTS_MAP[align || 'top'];
77
+ const alignment = doAlign({
78
+ overflow: {
79
+ adjustX: autoAlign,
80
+ adjustY: autoAlign
81
+ },
82
+ points,
83
+ sourceElement: sourceElement.current,
84
+ targetElement: targetElement.current
85
+ });
86
+ const alignmentString = alignment.points.join('');
87
+ const pointsString = points.join('');
88
+ if (alignment.overflow.adjustX) {
89
+ onAlign(ALIGNMENTS_FORCE_MAP[alignmentString]);
90
+ } else if (pointsString !== alignmentString) {
91
+ onAlign(ALIGNMENTS_INVERSE_MAP[alignmentString]);
92
+ }
93
+ }
94
+ }, [align, title, isOpen]);
95
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ */
5
+
6
+ import { useCallback, useRef } from 'react';
7
+ function matches(element, selectorString) {
8
+ if (element.matches) {
9
+ return element.matches(selectorString);
10
+ } else if (element.msMatchesSelector) {
11
+ return element.msMatchesSelector(selectorString);
12
+ } else if (element.webkitMatchesSelector) {
13
+ return element.webkitMatchesSelector(selectorString);
14
+ } else {
15
+ return false;
16
+ }
17
+ }
18
+ function closestAncestor(node, s) {
19
+ const element = node;
20
+ let ancestor = node;
21
+ if (!document.documentElement.contains(element)) {
22
+ return null;
23
+ }
24
+ do {
25
+ if (matches(ancestor, s)) {
26
+ return ancestor;
27
+ }
28
+ ancestor = ancestor.parentElement;
29
+ } while (ancestor !== null);
30
+ return null;
31
+ }
32
+ export function useClosestTitle(props) {
33
+ const targetRef = useRef(null);
34
+ const titleNodeRef = useRef(null);
35
+ const saveTitle = useCallback(element => {
36
+ const title = element.getAttribute('title');
37
+ if (title) {
38
+ element.setAttribute('data-restore-title', title);
39
+ element.removeAttribute('title');
40
+ } else if (element.tagName === 'svg') {
41
+ const titleTag = element.querySelector('title');
42
+ if (titleTag) {
43
+ element.setAttribute('data-restore-title', titleTag.innerHTML);
44
+ titleTag.remove();
45
+ }
46
+ }
47
+ const hasParentTitle = element.closest('[title]');
48
+ if (hasParentTitle) {
49
+ saveTitle(hasParentTitle);
50
+ }
51
+ }, []);
52
+ const restoreTitle = useCallback(element => {
53
+ const title = element.getAttribute('data-restore-title');
54
+ if (title) {
55
+ if (element.tagName === 'svg') {
56
+ const titleTag = document.createElement('title');
57
+ titleTag.innerHTML = title;
58
+ element.appendChild(titleTag);
59
+ } else {
60
+ element.setAttribute('title', title);
61
+ }
62
+ element.removeAttribute('data-restore-title');
63
+ }
64
+ const hasParentTitle = element.closest('[data-restore-title]');
65
+ if (hasParentTitle) {
66
+ restoreTitle(hasParentTitle);
67
+ }
68
+ }, []);
69
+ const onClick = useCallback(event => {
70
+ props.onClick();
71
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
72
+ onHide(event);
73
+ }, []);
74
+ const onHide = useCallback(event => {
75
+ if (event && !event.relatedTarget?.getAttribute('title') && (props.tooltipRef.current?.contains(event.relatedTarget) || targetRef.current?.contains(event.relatedTarget))) {
76
+ return null;
77
+ }
78
+ props.onHide();
79
+ if (titleNodeRef.current) {
80
+ restoreTitle(titleNodeRef.current);
81
+ titleNodeRef.current = null;
82
+ }
83
+ if (targetRef.current) {
84
+ targetRef.current.removeEventListener('click', onClick);
85
+ targetRef.current = null;
86
+ }
87
+ }, []);
88
+ const forceHide = useCallback(() => {
89
+ props.forceHide();
90
+ if (titleNodeRef.current) {
91
+ restoreTitle(titleNodeRef.current);
92
+ titleNodeRef.current = null;
93
+ }
94
+ if (targetRef.current) {
95
+ targetRef.current.removeEventListener('click', onClick);
96
+ targetRef.current = null;
97
+ }
98
+ }, []);
99
+ const getProps = useCallback((event, hideBrowserTitle) => {
100
+ if (targetRef.current) {
101
+ props.onClick();
102
+ if (onHide(event) === null) {
103
+ return;
104
+ }
105
+ }
106
+ const target = event.target;
107
+ const hasTitle = target && (target.hasAttribute('[title]') || target.hasAttribute('[data-title]'));
108
+ const node = hasTitle ? target : closestAncestor(target, '[title], [data-title]');
109
+ const hasNonEmptyTitle = node?.getAttribute('title') !== '';
110
+ if (node && hasNonEmptyTitle) {
111
+ targetRef.current = target;
112
+ target.addEventListener('click', onClick);
113
+ const title = node.getAttribute('title') || node.getAttribute('data-title') || '';
114
+ titleNodeRef.current = node;
115
+ if (hideBrowserTitle) {
116
+ saveTitle(node);
117
+ }
118
+ return {
119
+ align: node.getAttribute('data-tooltip-align'),
120
+ delay: node.getAttribute('data-tooltip-delay'),
121
+ floating: Boolean(node.getAttribute('data-tooltip-floating')),
122
+ setAsHTML: !!node.getAttribute('data-title-set-as-html'),
123
+ title
124
+ };
125
+ }
126
+ }, []);
127
+ return {
128
+ forceHide,
129
+ getProps,
130
+ onHide,
131
+ target: targetRef,
132
+ titleNode: titleNodeRef
133
+ };
134
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
3
+ * SPDX-License-Identifier: BSD-3-Clause
4
+ */
5
+
6
+ import { useCallback, useRef, useState } from 'react';
7
+ export function useTooltipState(_ref) {
8
+ let {
9
+ delay = 600
10
+ } = _ref;
11
+ const [isOpen, setOpen] = useState(false);
12
+ const timeoutIdRef = useRef();
13
+ const open = useCallback((immediate, customDelay) => {
14
+ if (!immediate) {
15
+ clearTimeout(timeoutIdRef.current);
16
+ timeoutIdRef.current = setTimeout(() => {
17
+ setOpen(true);
18
+ }, customDelay !== undefined ? customDelay : delay);
19
+ } else {
20
+ setOpen(true);
21
+ }
22
+ }, []);
23
+ const close = useCallback(() => {
24
+ clearTimeout(timeoutIdRef.current);
25
+ setOpen(false);
26
+ }, []);
27
+ return {
28
+ close,
29
+ isOpen,
30
+ open
31
+ };
32
+ }
package/package.json CHANGED
@@ -1,21 +1,24 @@
1
1
  {
2
2
  "name": "@clayui/tooltip",
3
- "version": "3.142.0",
3
+ "version": "3.143.0",
4
4
  "description": "ClayTooltip component",
5
5
  "license": "BSD-3-Clause",
6
6
  "repository": "https://github.com/liferay/clay",
7
- "engines": {
8
- "node": ">=0.12.0",
9
- "npm": ">=3.0.0"
7
+ "main": "lib/cjs/index.js",
8
+ "module": "lib/esm/index.js",
9
+ "exports": {
10
+ "import": "./lib/esm/index.js",
11
+ "require": "./lib/cjs/index.js"
10
12
  },
11
- "main": "lib/index.js",
12
13
  "types": "lib/index.d.ts",
13
14
  "ts:main": "src/index.tsx",
14
15
  "files": [
15
16
  "lib"
16
17
  ],
17
18
  "scripts": {
18
- "build": "cross-env NODE_ENV=production babel src --root-mode upward --out-dir lib --extensions .ts,.tsx",
19
+ "build": "yarn build:cjs && yarn build:esm",
20
+ "build:cjs": "cross-env NODE_ENV=production babel src --root-mode upward --out-dir lib/cjs --extensions .ts,.tsx",
21
+ "build:esm": "cross-env NODE_ENV=production babel src --root-mode upward --out-dir lib/esm --extensions .ts,.tsx --env-name esm",
19
22
  "buildTypes": "cross-env NODE_ENV=production tsc --project ./tsconfig.declarations.json",
20
23
  "format": "prettier --write \"**/*.{js,ts,tsx,md,mdx,json,scss}\"",
21
24
  "test": "jest --config ../../jest.config.js"
@@ -25,7 +28,7 @@
25
28
  "react"
26
29
  ],
27
30
  "dependencies": {
28
- "@clayui/shared": "^3.142.0",
31
+ "@clayui/shared": "^3.143.0",
29
32
  "classnames": "^2.2.6",
30
33
  "dom-align": "^1.12.2",
31
34
  "warning": "^4.0.3"
@@ -38,5 +41,5 @@
38
41
  "browserslist": [
39
42
  "extends browserslist-config-clay"
40
43
  ],
41
- "gitHead": "f563be12a87d6fcf03706d235618e5512de63463"
44
+ "gitHead": "aacf20646cc7fb25c4d60e865ec77d2d503d23e9"
42
45
  }
package/lib/Tooltip.js DELETED
@@ -1,39 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.Tooltip = exports.ALIGN_POSITIONS = void 0;
7
- var _classnames = _interopRequireDefault(require("classnames"));
8
- var _react = _interopRequireDefault(require("react"));
9
- var _excluded = ["alignPosition", "children", "className", "show"];
10
- /**
11
- * SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
12
- * SPDX-License-Identifier: BSD-3-Clause
13
- */
14
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
- function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) { ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } } return n; }, _extends.apply(null, arguments); }
16
- function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var s = Object.getOwnPropertySymbols(e); for (r = 0; r < s.length; r++) { o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } } return i; }
17
- function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) { if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } } return t; }
18
- var ALIGN_POSITIONS = exports.ALIGN_POSITIONS = ['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'right'];
19
- var Tooltip = exports.Tooltip = /*#__PURE__*/_react.default.forwardRef(function (_ref, ref) {
20
- var _ref$alignPosition = _ref.alignPosition,
21
- alignPosition = _ref$alignPosition === void 0 ? 'bottom' : _ref$alignPosition,
22
- children = _ref.children,
23
- className = _ref.className,
24
- show = _ref.show,
25
- otherProps = _objectWithoutProperties(_ref, _excluded);
26
- return /*#__PURE__*/_react.default.createElement("div", _extends({
27
- className: (0, _classnames.default)(className, 'tooltip', "clay-tooltip-".concat(alignPosition), {
28
- show: show
29
- }),
30
- role: "tooltip"
31
- }, otherProps, {
32
- ref: ref
33
- }), /*#__PURE__*/_react.default.createElement("div", {
34
- className: "arrow"
35
- }), /*#__PURE__*/_react.default.createElement("div", {
36
- className: "tooltip-inner"
37
- }, children));
38
- });
39
- Tooltip.displayName = 'ClayTooltip';