@clayui/tooltip 3.75.2 → 3.78.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.
- package/lib/TooltipProvider.js +124 -229
- package/lib/useAlign.d.ts +18 -0
- package/lib/useAlign.js +121 -0
- package/lib/useClosestTitle.d.ts +23 -0
- package/lib/useClosestTitle.js +138 -0
- package/lib/useTooltipState.d.ts +13 -0
- package/lib/useTooltipState.js +51 -0
- package/package.json +3 -3
- package/src/TooltipProvider.tsx +128 -307
- package/src/__tests__/index.tsx +28 -0
- package/src/useAlign.ts +137 -0
- package/src/useClosestTitle.ts +170 -0
- package/src/useTooltipState.ts +43 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2019 Liferay, Inc. <https://liferay.com>
|
|
3
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, {useCallback, useRef} from 'react';
|
|
7
|
+
|
|
8
|
+
function matches(
|
|
9
|
+
element: HTMLElement & {
|
|
10
|
+
msMatchesSelector?: HTMLElement['matches'];
|
|
11
|
+
},
|
|
12
|
+
selectorString: string
|
|
13
|
+
) {
|
|
14
|
+
if (element.matches) {
|
|
15
|
+
return element.matches(selectorString);
|
|
16
|
+
} else if (element.msMatchesSelector) {
|
|
17
|
+
return element.msMatchesSelector(selectorString);
|
|
18
|
+
} else if (element.webkitMatchesSelector) {
|
|
19
|
+
return element.webkitMatchesSelector(selectorString);
|
|
20
|
+
} else {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function closestAncestor(node: HTMLElement, s: string) {
|
|
26
|
+
const element = node;
|
|
27
|
+
let ancestor: HTMLElement | null = node;
|
|
28
|
+
|
|
29
|
+
if (!document.documentElement.contains(element)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
do {
|
|
34
|
+
if (matches(ancestor, s)) {
|
|
35
|
+
return ancestor;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ancestor = ancestor.parentElement;
|
|
39
|
+
} while (ancestor !== null);
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type Props = {
|
|
45
|
+
onHide: () => void;
|
|
46
|
+
onClick: () => void;
|
|
47
|
+
tooltipRef: React.MutableRefObject<HTMLElement | null>;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function useClosestTitle(props: Props) {
|
|
51
|
+
const targetRef = useRef<HTMLElement | null>(null);
|
|
52
|
+
const titleNodeRef = useRef<HTMLElement | null>(null);
|
|
53
|
+
|
|
54
|
+
const saveTitle = useCallback((element: HTMLElement) => {
|
|
55
|
+
titleNodeRef.current = element;
|
|
56
|
+
|
|
57
|
+
const title = element.getAttribute('title');
|
|
58
|
+
|
|
59
|
+
if (title) {
|
|
60
|
+
element.setAttribute('data-restore-title', title);
|
|
61
|
+
element.removeAttribute('title');
|
|
62
|
+
} else if (element.tagName === 'svg') {
|
|
63
|
+
const titleTag = element.querySelector('title');
|
|
64
|
+
|
|
65
|
+
if (titleTag) {
|
|
66
|
+
element.setAttribute('data-restore-title', titleTag.innerHTML);
|
|
67
|
+
|
|
68
|
+
titleTag.remove();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
const restoreTitle = useCallback(() => {
|
|
74
|
+
const element = titleNodeRef.current;
|
|
75
|
+
|
|
76
|
+
if (element) {
|
|
77
|
+
const title = element.getAttribute('data-restore-title');
|
|
78
|
+
|
|
79
|
+
if (title) {
|
|
80
|
+
if (element.tagName === 'svg') {
|
|
81
|
+
const titleTag = document.createElement('title');
|
|
82
|
+
|
|
83
|
+
titleTag.innerHTML = title;
|
|
84
|
+
|
|
85
|
+
element.appendChild(titleTag);
|
|
86
|
+
} else {
|
|
87
|
+
element.setAttribute('title', title);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
element.removeAttribute('data-restore-title');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
titleNodeRef.current = null;
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const onClick = useCallback((event?: any) => {
|
|
98
|
+
props.onClick();
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
100
|
+
onHide(event);
|
|
101
|
+
}, []);
|
|
102
|
+
|
|
103
|
+
const onHide = useCallback((event?: any) => {
|
|
104
|
+
if (
|
|
105
|
+
event &&
|
|
106
|
+
(props.tooltipRef.current?.contains(event.relatedTarget) ||
|
|
107
|
+
targetRef.current?.contains(event.relatedTarget))
|
|
108
|
+
) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
props.onHide();
|
|
113
|
+
|
|
114
|
+
restoreTitle();
|
|
115
|
+
|
|
116
|
+
if (targetRef.current) {
|
|
117
|
+
targetRef.current.removeEventListener('click', onClick);
|
|
118
|
+
targetRef.current = null;
|
|
119
|
+
}
|
|
120
|
+
}, []);
|
|
121
|
+
|
|
122
|
+
const getProps = useCallback(
|
|
123
|
+
(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
|
124
|
+
if (targetRef.current) {
|
|
125
|
+
props.onClick();
|
|
126
|
+
|
|
127
|
+
if (onHide(event) === null) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const target = event.target as HTMLElement;
|
|
133
|
+
|
|
134
|
+
const hasTitle =
|
|
135
|
+
target &&
|
|
136
|
+
(target.hasAttribute('[title]') ||
|
|
137
|
+
target.hasAttribute('[data-title]'));
|
|
138
|
+
|
|
139
|
+
const node = hasTitle
|
|
140
|
+
? target
|
|
141
|
+
: closestAncestor(target, '[title], [data-title]');
|
|
142
|
+
|
|
143
|
+
if (node) {
|
|
144
|
+
targetRef.current = target;
|
|
145
|
+
|
|
146
|
+
target.addEventListener('click', onClick);
|
|
147
|
+
|
|
148
|
+
const title =
|
|
149
|
+
node.getAttribute('title') ||
|
|
150
|
+
node.getAttribute('data-title') ||
|
|
151
|
+
'';
|
|
152
|
+
|
|
153
|
+
saveTitle(node);
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
align: node.getAttribute('data-tooltip-align'),
|
|
157
|
+
delay: node.getAttribute('data-tooltip-delay'),
|
|
158
|
+
floating: Boolean(
|
|
159
|
+
node.getAttribute('data-tooltip-floating')
|
|
160
|
+
),
|
|
161
|
+
setAsHTML: !!node.getAttribute('data-title-set-as-html'),
|
|
162
|
+
title,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
[]
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return {getProps, onHide, target: targetRef, titleNode: titleNodeRef};
|
|
170
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
delay?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function useTooltipState({delay = 600}: Props) {
|
|
13
|
+
const [isOpen, setOpen] = useState(false);
|
|
14
|
+
|
|
15
|
+
const timeoutIdRef = useRef<any>();
|
|
16
|
+
|
|
17
|
+
const open = useCallback((immediate: boolean, customDelay?: number) => {
|
|
18
|
+
if (!immediate) {
|
|
19
|
+
clearTimeout(timeoutIdRef.current);
|
|
20
|
+
|
|
21
|
+
timeoutIdRef.current = setTimeout(
|
|
22
|
+
() => {
|
|
23
|
+
setOpen(true);
|
|
24
|
+
},
|
|
25
|
+
customDelay !== undefined ? customDelay : delay
|
|
26
|
+
);
|
|
27
|
+
} else {
|
|
28
|
+
setOpen(true);
|
|
29
|
+
}
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
const close = useCallback(() => {
|
|
33
|
+
clearTimeout(timeoutIdRef.current);
|
|
34
|
+
|
|
35
|
+
setOpen(false);
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
close,
|
|
40
|
+
isOpen,
|
|
41
|
+
open,
|
|
42
|
+
};
|
|
43
|
+
}
|