|
1 |
| -import React, { createContext, PropsWithChildren, useContext, useMemo, useState } from 'react' |
| 1 | +import React, { |
| 2 | + createContext, |
| 3 | + PropsWithChildren, |
| 4 | + useCallback, |
| 5 | + useContext, |
| 6 | + useId, |
| 7 | + useState, |
| 8 | +} from 'react' |
| 9 | + |
| 10 | +type AnchorRef = React.RefObject<HTMLElement> |
2 | 11 |
|
3 | 12 | interface TooltipContextData {
|
4 |
| - anchorRefs: Set<React.RefObject<HTMLElement>> |
5 |
| - attach: (...ref: React.RefObject<HTMLElement>[]) => void |
6 |
| - detach: (...ref: React.RefObject<HTMLElement>[]) => void |
7 |
| - activeAnchor: React.RefObject<HTMLElement> |
8 |
| - setActiveAnchor: React.Dispatch<React.SetStateAction<React.RefObject<HTMLElement>>> |
| 13 | + // when using one tooltip |
| 14 | + anchorRefs: Set<AnchorRef> |
| 15 | + activeAnchor: AnchorRef |
| 16 | + attach: (...refs: AnchorRef[]) => void |
| 17 | + detach: (...refs: AnchorRef[]) => void |
| 18 | + setActiveAnchor: (ref: AnchorRef) => void |
9 | 19 | }
|
10 | 20 |
|
11 |
| -const TooltipContext = createContext<TooltipContextData>({ |
| 21 | +type TooltipContextDataBuilder = (tooltipId?: string) => TooltipContextData |
| 22 | + |
| 23 | +const TooltipContext = createContext<TooltipContextDataBuilder>(() => ({ |
12 | 24 | anchorRefs: new Set(),
|
| 25 | + activeAnchor: { current: null }, |
13 | 26 | attach: () => {
|
14 | 27 | /* attach anchor element */
|
15 | 28 | },
|
16 | 29 | detach: () => {
|
17 | 30 | /* detach anchor element */
|
18 | 31 | },
|
19 |
| - activeAnchor: { current: null }, |
20 | 32 | setActiveAnchor: () => {
|
21 | 33 | /* set active anchor */
|
22 | 34 | },
|
23 |
| -}) |
| 35 | +})) |
24 | 36 |
|
25 | 37 | const TooltipProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
26 |
| - const [anchorRefs, setAnchorRefs] = useState<Set<React.RefObject<HTMLElement>>>(new Set()) |
27 |
| - const [activeAnchor, setActiveAnchor] = useState<React.RefObject<HTMLElement>>({ current: null }) |
28 |
| - |
29 |
| - const attach = (...refs: React.RefObject<HTMLElement>[]) => { |
30 |
| - setAnchorRefs((oldRefs) => { |
31 |
| - refs.forEach((ref) => oldRefs.add(ref)) |
32 |
| - // create new set to trigger re-render |
33 |
| - return new Set(oldRefs) |
| 38 | + const defaultTooltipId = useId() |
| 39 | + const [anchorRefMap, setAnchorRefMap] = useState<Record<string, Set<AnchorRef>>>({ |
| 40 | + [defaultTooltipId]: new Set(), |
| 41 | + }) |
| 42 | + const [activeAnchorMap, setActiveAnchorMap] = useState<Record<string, AnchorRef>>({ |
| 43 | + [defaultTooltipId]: { current: null }, |
| 44 | + }) |
| 45 | + |
| 46 | + const attach = (tooltipId: string, ...refs: AnchorRef[]) => { |
| 47 | + setAnchorRefMap((oldMap) => { |
| 48 | + const tooltipRefs = oldMap[tooltipId] ?? new Set() |
| 49 | + refs.forEach((ref) => tooltipRefs.add(ref)) |
| 50 | + // create new object to trigger re-render |
| 51 | + return { ...oldMap, [tooltipId]: tooltipRefs } |
| 52 | + }) |
| 53 | + } |
| 54 | + |
| 55 | + const detach = (tooltipId: string, ...refs: AnchorRef[]) => { |
| 56 | + setAnchorRefMap((oldMap) => { |
| 57 | + const tooltipRefs = oldMap[tooltipId] |
| 58 | + if (!tooltipRefs) { |
| 59 | + // tooltip not found |
| 60 | + // maybe thow error? |
| 61 | + return oldMap |
| 62 | + } |
| 63 | + refs.forEach((ref) => tooltipRefs.delete(ref)) |
| 64 | + // create new object to trigger re-render |
| 65 | + return { ...oldMap } |
34 | 66 | })
|
35 | 67 | }
|
36 | 68 |
|
37 |
| - const detach = (...refs: React.RefObject<HTMLElement>[]) => { |
38 |
| - setAnchorRefs((oldRefs) => { |
39 |
| - refs.forEach((ref) => oldRefs.delete(ref)) |
40 |
| - // create new set to trigger re-render |
41 |
| - return new Set(oldRefs) |
| 69 | + const setActiveAnchor = (tooltipId: string, ref: React.RefObject<HTMLElement>) => { |
| 70 | + setActiveAnchorMap((oldMap) => { |
| 71 | + // create new object to trigger re-render |
| 72 | + return { ...oldMap, [tooltipId]: ref } |
42 | 73 | })
|
43 | 74 | }
|
44 | 75 |
|
45 |
| - const context = useMemo( |
46 |
| - () => ({ anchorRefs, attach, detach, activeAnchor, setActiveAnchor }), |
47 |
| - [anchorRefs, attach, detach, activeAnchor, setActiveAnchor], |
| 76 | + const context = useCallback( |
| 77 | + (tooltipId?: string) => ({ |
| 78 | + anchorRefs: anchorRefMap[tooltipId ?? defaultTooltipId] ?? new Set(), |
| 79 | + activeAnchor: activeAnchorMap[tooltipId ?? defaultTooltipId] ?? { current: null }, |
| 80 | + attach: (...refs: AnchorRef[]) => attach(tooltipId ?? defaultTooltipId, ...refs), |
| 81 | + detach: (...refs: AnchorRef[]) => detach(tooltipId ?? defaultTooltipId, ...refs), |
| 82 | + setActiveAnchor: (ref: AnchorRef) => setActiveAnchor(tooltipId ?? defaultTooltipId, ref), |
| 83 | + }), |
| 84 | + [defaultTooltipId, anchorRefMap, activeAnchorMap, attach, detach], |
48 | 85 | )
|
49 | 86 |
|
50 | 87 | return <TooltipContext.Provider value={context}>{children}</TooltipContext.Provider>
|
|
0 commit comments