@proyecto-viviana/solidaria-components 0.2.9 → 0.3.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/README.md +39 -272
- package/dist/ActionBar.d.ts +21 -13
- package/dist/ActionBar.d.ts.map +1 -1
- package/dist/ActionGroup.d.ts +8 -8
- package/dist/ActionGroup.d.ts.map +1 -1
- package/dist/Alert.d.ts +5 -5
- package/dist/Alert.d.ts.map +1 -1
- package/dist/Autocomplete.d.ts +5 -5
- package/dist/Autocomplete.d.ts.map +1 -1
- package/dist/Breadcrumbs.d.ts +18 -7
- package/dist/Breadcrumbs.d.ts.map +1 -1
- package/dist/Button.d.ts +24 -5
- package/dist/Button.d.ts.map +1 -1
- package/dist/Calendar.d.ts +38 -7
- package/dist/Calendar.d.ts.map +1 -1
- package/dist/Checkbox.d.ts +32 -7
- package/dist/Checkbox.d.ts.map +1 -1
- package/dist/Collection.d.ts +19 -14
- package/dist/Collection.d.ts.map +1 -1
- package/dist/Color.d.ts +103 -14
- package/dist/Color.d.ts.map +1 -1
- package/dist/ColorEditor.d.ts +6 -6
- package/dist/ColorEditor.d.ts.map +1 -1
- package/dist/ComboBox.d.ts +85 -19
- package/dist/ComboBox.d.ts.map +1 -1
- package/dist/ContextualHelpTrigger.d.ts +2 -2
- package/dist/ContextualHelpTrigger.d.ts.map +1 -1
- package/dist/DateField.d.ts +8 -6
- package/dist/DateField.d.ts.map +1 -1
- package/dist/DatePicker.d.ts +53 -22
- package/dist/DatePicker.d.ts.map +1 -1
- package/dist/DateRangePickerContext.d.ts +30 -0
- package/dist/DateRangePickerContext.d.ts.map +1 -0
- package/dist/Dialog.d.ts +5 -5
- package/dist/Dialog.d.ts.map +1 -1
- package/dist/Disclosure.d.ts +23 -5
- package/dist/Disclosure.d.ts.map +1 -1
- package/dist/DragAndDrop.d.ts +6 -6
- package/dist/DragAndDrop.d.ts.map +1 -1
- package/dist/DragPreview.d.ts +2 -2
- package/dist/DragPreview.d.ts.map +1 -1
- package/dist/DropZone.d.ts +4 -4
- package/dist/DropZone.d.ts.map +1 -1
- package/dist/FieldError.d.ts +9 -5
- package/dist/FieldError.d.ts.map +1 -1
- package/dist/FileTrigger.d.ts +3 -3
- package/dist/FileTrigger.d.ts.map +1 -1
- package/dist/Focusable.d.ts +2 -2
- package/dist/Focusable.d.ts.map +1 -1
- package/dist/Form.d.ts +18 -4
- package/dist/Form.d.ts.map +1 -1
- package/dist/GridList.d.ts +32 -12
- package/dist/GridList.d.ts.map +1 -1
- package/dist/HiddenDateInput.d.ts +26 -0
- package/dist/HiddenDateInput.d.ts.map +1 -0
- package/dist/HiddenTimeInput.d.ts +25 -0
- package/dist/HiddenTimeInput.d.ts.map +1 -0
- package/dist/Icon.d.ts +5 -5
- package/dist/Icon.d.ts.map +1 -1
- package/dist/Keyboard.d.ts +1 -1
- package/dist/Landmark.d.ts +3 -3
- package/dist/Landmark.d.ts.map +1 -1
- package/dist/Link.d.ts +10 -4
- package/dist/Link.d.ts.map +1 -1
- package/dist/ListBox.d.ts +32 -12
- package/dist/ListBox.d.ts.map +1 -1
- package/dist/ListDropTargetDelegate.d.ts +6 -6
- package/dist/ListDropTargetDelegate.d.ts.map +1 -1
- package/dist/Menu.d.ts +65 -14
- package/dist/Menu.d.ts.map +1 -1
- package/dist/Meter.d.ts +3 -3
- package/dist/Meter.d.ts.map +1 -1
- package/dist/Modal.d.ts +5 -5
- package/dist/Modal.d.ts.map +1 -1
- package/dist/NumberField.d.ts +8 -12
- package/dist/NumberField.d.ts.map +1 -1
- package/dist/Popover.d.ts +28 -5
- package/dist/Popover.d.ts.map +1 -1
- package/dist/Pressable.d.ts +2 -2
- package/dist/Pressable.d.ts.map +1 -1
- package/dist/ProgressBar.d.ts +5 -3
- package/dist/ProgressBar.d.ts.map +1 -1
- package/dist/RadioGroup.d.ts +43 -9
- package/dist/RadioGroup.d.ts.map +1 -1
- package/dist/RangeCalendar.d.ts +34 -7
- package/dist/RangeCalendar.d.ts.map +1 -1
- package/dist/RouterProvider.d.ts +2 -2
- package/dist/RouterProvider.d.ts.map +1 -1
- package/dist/SearchField.d.ts +23 -20
- package/dist/SearchField.d.ts.map +1 -1
- package/dist/Select.d.ts +41 -11
- package/dist/Select.d.ts.map +1 -1
- package/dist/SelectionIndicator.d.ts +3 -3
- package/dist/SelectionIndicator.d.ts.map +1 -1
- package/dist/Separator.d.ts +9 -3
- package/dist/Separator.d.ts.map +1 -1
- package/dist/SharedElementTransition.d.ts +6 -4
- package/dist/SharedElementTransition.d.ts.map +1 -1
- package/dist/Slider.d.ts +12 -8
- package/dist/Slider.d.ts.map +1 -1
- package/dist/StepList.d.ts +90 -0
- package/dist/StepList.d.ts.map +1 -0
- package/dist/Switch.d.ts +11 -5
- package/dist/Switch.d.ts.map +1 -1
- package/dist/Table.d.ts +187 -23
- package/dist/Table.d.ts.map +1 -1
- package/dist/Tabs.d.ts +45 -9
- package/dist/Tabs.d.ts.map +1 -1
- package/dist/TagGroup.d.ts +12 -10
- package/dist/TagGroup.d.ts.map +1 -1
- package/dist/Text.d.ts +2 -2
- package/dist/TextField.d.ts +15 -11
- package/dist/TextField.d.ts.map +1 -1
- package/dist/TimeField.d.ts +6 -6
- package/dist/TimeField.d.ts.map +1 -1
- package/dist/Toast.d.ts +29 -14
- package/dist/Toast.d.ts.map +1 -1
- package/dist/ToggleButton.d.ts +11 -5
- package/dist/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButtonGroup.d.ts +7 -7
- package/dist/ToggleButtonGroup.d.ts.map +1 -1
- package/dist/Toolbar.d.ts +7 -3
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Tooltip.d.ts +50 -8
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/Tree.d.ts +66 -17
- package/dist/Tree.d.ts.map +1 -1
- package/dist/Virtualizer.d.ts +12 -12
- package/dist/Virtualizer.d.ts.map +1 -1
- package/dist/VirtualizerLayouts.d.ts +2 -2
- package/dist/VirtualizerLayouts.d.ts.map +1 -1
- package/dist/VisuallyHidden.d.ts +1 -1
- package/dist/VisuallyHidden.d.ts.map +1 -1
- package/dist/contexts.d.ts +5 -1
- package/dist/contexts.d.ts.map +1 -1
- package/dist/index.d.ts +73 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23247 -18564
- package/dist/index.js.map +1 -1
- package/dist/index.jsx +18110 -0
- package/dist/index.jsx.map +1 -0
- package/dist/useDragAndDrop.d.ts +13 -13
- package/dist/useDragAndDrop.d.ts.map +1 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/virtualizer/Layout.d.ts +1 -1
- package/dist/virtualizer/Layout.d.ts.map +1 -1
- package/package.json +31 -32
- package/src/ActionBar.tsx +75 -72
- package/src/ActionGroup.tsx +53 -61
- package/src/Alert.tsx +17 -42
- package/src/Autocomplete.tsx +39 -44
- package/src/Breadcrumbs.tsx +149 -80
- package/src/Button.tsx +267 -70
- package/src/Calendar.tsx +218 -138
- package/src/Checkbox.tsx +413 -121
- package/src/Collection.tsx +67 -58
- package/src/Color.tsx +803 -380
- package/src/ColorEditor.tsx +131 -149
- package/src/ComboBox.tsx +414 -249
- package/src/ContextualHelpTrigger.tsx +86 -74
- package/src/DateField.tsx +185 -91
- package/src/DatePicker.tsx +524 -213
- package/src/DateRangePickerContext.tsx +44 -0
- package/src/Dialog.tsx +156 -118
- package/src/Disclosure.tsx +127 -80
- package/src/DragAndDrop.tsx +60 -54
- package/src/DragPreview.tsx +13 -11
- package/src/DropZone.tsx +42 -22
- package/src/FieldError.tsx +45 -23
- package/src/FileTrigger.tsx +19 -19
- package/src/Focusable.tsx +21 -24
- package/src/Form.tsx +71 -16
- package/src/GridList.tsx +273 -197
- package/src/HiddenDateInput.tsx +153 -0
- package/src/HiddenTimeInput.tsx +133 -0
- package/src/Icon.tsx +22 -43
- package/src/Keyboard.tsx +3 -3
- package/src/Landmark.tsx +37 -63
- package/src/Link.tsx +125 -75
- package/src/ListBox.tsx +332 -233
- package/src/ListDropTargetDelegate.ts +81 -80
- package/src/Menu.tsx +1023 -274
- package/src/Meter.tsx +38 -56
- package/src/Modal.tsx +243 -175
- package/src/NumberField.tsx +139 -143
- package/src/Popover.tsx +386 -233
- package/src/Pressable.tsx +21 -21
- package/src/ProgressBar.tsx +48 -57
- package/src/RadioGroup.tsx +524 -122
- package/src/RangeCalendar.tsx +157 -90
- package/src/RouterProvider.tsx +30 -47
- package/src/SearchField.tsx +362 -143
- package/src/Select.tsx +656 -233
- package/src/SelectionIndicator.tsx +18 -15
- package/src/Separator.tsx +47 -49
- package/src/SharedElementTransition.tsx +103 -97
- package/src/Slider.tsx +138 -98
- package/src/StepList.tsx +272 -0
- package/src/Switch.tsx +93 -46
- package/src/Table.tsx +1308 -342
- package/src/Tabs.tsx +324 -103
- package/src/TagGroup.tsx +139 -126
- package/src/Text.tsx +3 -3
- package/src/TextField.tsx +389 -79
- package/src/TimeField.tsx +136 -76
- package/src/Toast.tsx +209 -157
- package/src/ToggleButton.tsx +47 -37
- package/src/ToggleButtonGroup.tsx +39 -34
- package/src/Toolbar.tsx +54 -69
- package/src/Tooltip.tsx +387 -119
- package/src/Tree.tsx +651 -368
- package/src/Virtualizer.tsx +208 -180
- package/src/VirtualizerLayouts.ts +45 -30
- package/src/VisuallyHidden.tsx +19 -19
- package/src/contexts.ts +29 -37
- package/src/index.ts +110 -195
- package/src/useDragAndDrop.ts +87 -71
- package/src/utils.tsx +40 -55
- package/src/virtualizer/Layout.ts +14 -22
- package/dist/index.ssr.js +0 -16996
- package/dist/index.ssr.js.map +0 -1
package/src/Tooltip.tsx
CHANGED
|
@@ -15,19 +15,19 @@ import {
|
|
|
15
15
|
createEffect,
|
|
16
16
|
onCleanup,
|
|
17
17
|
Show,
|
|
18
|
-
} from
|
|
19
|
-
import { isServer } from
|
|
18
|
+
} from "solid-js";
|
|
19
|
+
import { isServer } from "solid-js/web";
|
|
20
20
|
import {
|
|
21
21
|
createTooltipTriggerState,
|
|
22
22
|
type TooltipTriggerState,
|
|
23
23
|
type TooltipTriggerProps as StateProps,
|
|
24
|
-
} from
|
|
24
|
+
} from "@proyecto-viviana/solid-stately";
|
|
25
25
|
import {
|
|
26
26
|
createTooltip,
|
|
27
27
|
createTooltipTrigger,
|
|
28
28
|
type TooltipTriggerProps as AriaProps,
|
|
29
29
|
OverlayContainer,
|
|
30
|
-
} from
|
|
30
|
+
} from "@proyecto-viviana/solidaria";
|
|
31
31
|
import {
|
|
32
32
|
type RenderChildren,
|
|
33
33
|
type ClassNameOrFunction,
|
|
@@ -35,11 +35,7 @@ import {
|
|
|
35
35
|
type SlotProps,
|
|
36
36
|
useRenderProps,
|
|
37
37
|
filterDOMProps,
|
|
38
|
-
} from
|
|
39
|
-
|
|
40
|
-
// ============================================
|
|
41
|
-
// TYPES
|
|
42
|
-
// ============================================
|
|
38
|
+
} from "./utils";
|
|
43
39
|
|
|
44
40
|
export interface TooltipRenderProps {
|
|
45
41
|
/** Whether the tooltip is currently entering (for animations). */
|
|
@@ -47,15 +43,36 @@ export interface TooltipRenderProps {
|
|
|
47
43
|
/** Whether the tooltip is currently exiting (for animations). */
|
|
48
44
|
isExiting: boolean;
|
|
49
45
|
/** The placement of the tooltip relative to the trigger. */
|
|
50
|
-
placement:
|
|
46
|
+
placement: TooltipResolvedPlacement | null;
|
|
51
47
|
}
|
|
52
48
|
|
|
49
|
+
export type TooltipPlacement = "top" | "bottom" | "left" | "right" | "start" | "end";
|
|
50
|
+
export type TooltipResolvedPlacement = "top" | "bottom" | "left" | "right";
|
|
51
|
+
|
|
53
52
|
export interface TooltipTriggerComponentProps extends StateProps, AriaProps {
|
|
54
53
|
/** The children of the tooltip trigger (trigger element and tooltip). */
|
|
55
54
|
children: JSX.Element;
|
|
55
|
+
/** The placement of the tooltip relative to the trigger. */
|
|
56
|
+
placement?: TooltipPlacement;
|
|
57
|
+
/** The placement padding between the tooltip and viewport edge. */
|
|
58
|
+
containerPadding?: number;
|
|
59
|
+
/** The additional offset along the cross axis. */
|
|
60
|
+
crossOffset?: number;
|
|
61
|
+
/** Whether the tooltip should flip when there is insufficient room. */
|
|
62
|
+
shouldFlip?: boolean;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
export interface TooltipProps extends SlotProps {
|
|
66
|
+
/** The element id. */
|
|
67
|
+
id?: string;
|
|
68
|
+
/** Custom aria-label for the tooltip. */
|
|
69
|
+
"aria-label"?: string;
|
|
70
|
+
/** ID of an element that labels the tooltip. */
|
|
71
|
+
"aria-labelledby"?: string;
|
|
72
|
+
/** ID of an element that describes the tooltip. */
|
|
73
|
+
"aria-describedby"?: string;
|
|
74
|
+
/** ID of an element that provides details for the tooltip. */
|
|
75
|
+
"aria-details"?: string;
|
|
59
76
|
/** The children of the tooltip. A function may be provided to receive render props. */
|
|
60
77
|
children?: RenderChildren<TooltipRenderProps>;
|
|
61
78
|
/** The CSS className for the element. */
|
|
@@ -67,29 +84,43 @@ export interface TooltipProps extends SlotProps {
|
|
|
67
84
|
/** Whether the tooltip is open by default (uncontrolled). */
|
|
68
85
|
defaultOpen?: boolean;
|
|
69
86
|
/** The placement of the tooltip relative to the trigger. */
|
|
70
|
-
placement?:
|
|
71
|
-
/**
|
|
87
|
+
placement?: TooltipPlacement;
|
|
88
|
+
/** The placement padding between the tooltip and viewport edge. */
|
|
89
|
+
containerPadding?: number;
|
|
90
|
+
/** The additional offset along the cross axis. */
|
|
91
|
+
crossOffset?: number;
|
|
92
|
+
/** Whether the tooltip should flip when there is insufficient room. */
|
|
72
93
|
shouldFlip?: boolean;
|
|
94
|
+
/** The offset between the tooltip and trigger. */
|
|
95
|
+
offset?: number;
|
|
96
|
+
/** The arrow size used to keep the arrow overlapping the trigger. */
|
|
97
|
+
arrowSize?: number;
|
|
98
|
+
/** The padding between the arrow and tooltip edge. */
|
|
99
|
+
arrowBoundaryOffset?: number;
|
|
100
|
+
/** Whether the tooltip should be disabled. */
|
|
101
|
+
isDisabled?: boolean;
|
|
102
|
+
/** The element language. */
|
|
103
|
+
lang?: string;
|
|
104
|
+
/** The text direction. */
|
|
105
|
+
dir?: "ltr" | "rtl";
|
|
73
106
|
}
|
|
74
107
|
|
|
75
|
-
// ============================================
|
|
76
|
-
// CONTEXT
|
|
77
|
-
// ============================================
|
|
78
|
-
|
|
79
108
|
interface TooltipTriggerContextValue {
|
|
80
109
|
state: TooltipTriggerState;
|
|
81
|
-
tooltipProps: { id: string };
|
|
110
|
+
tooltipProps: { readonly id: string };
|
|
111
|
+
setTooltipId: (id: string | undefined) => void;
|
|
82
112
|
triggerRef: () => HTMLElement | null | undefined;
|
|
113
|
+
placement: () => TooltipPlacement | undefined;
|
|
114
|
+
containerPadding: () => number | undefined;
|
|
115
|
+
crossOffset: () => number | undefined;
|
|
116
|
+
shouldFlip: () => boolean | undefined;
|
|
117
|
+
isDisabled: () => boolean | undefined;
|
|
83
118
|
}
|
|
84
119
|
|
|
85
120
|
const TooltipTriggerContext = createContext<TooltipTriggerContextValue | null>(null);
|
|
86
121
|
export const TooltipContext = TooltipTriggerContext;
|
|
87
122
|
export const TooltipTriggerStateContext = createContext<TooltipTriggerState | null>(null);
|
|
88
123
|
|
|
89
|
-
// ============================================
|
|
90
|
-
// COMPONENTS
|
|
91
|
-
// ============================================
|
|
92
|
-
|
|
93
124
|
/**
|
|
94
125
|
* TooltipTrigger wraps around a trigger element and a Tooltip.
|
|
95
126
|
* It handles opening and closing the Tooltip when the user hovers
|
|
@@ -105,42 +136,68 @@ export const TooltipTriggerStateContext = createContext<TooltipTriggerState | nu
|
|
|
105
136
|
*/
|
|
106
137
|
export const TooltipTrigger: ParentComponent<TooltipTriggerComponentProps> = (props) => {
|
|
107
138
|
let triggerRef: HTMLElement | null = null;
|
|
139
|
+
const [tooltipId, setTooltipId] = createSignal<string | undefined>();
|
|
108
140
|
|
|
109
141
|
const state = createTooltipTriggerState({
|
|
110
|
-
get delay() {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
get
|
|
114
|
-
|
|
142
|
+
get delay() {
|
|
143
|
+
return props.delay;
|
|
144
|
+
},
|
|
145
|
+
get closeDelay() {
|
|
146
|
+
return props.closeDelay;
|
|
147
|
+
},
|
|
148
|
+
get isOpen() {
|
|
149
|
+
return props.isOpen;
|
|
150
|
+
},
|
|
151
|
+
get defaultOpen() {
|
|
152
|
+
return props.defaultOpen;
|
|
153
|
+
},
|
|
154
|
+
get onOpenChange() {
|
|
155
|
+
return props.onOpenChange;
|
|
156
|
+
},
|
|
115
157
|
});
|
|
116
158
|
|
|
117
159
|
const { triggerProps, tooltipProps } = createTooltipTrigger(
|
|
118
160
|
{
|
|
119
|
-
get isDisabled() {
|
|
120
|
-
|
|
121
|
-
|
|
161
|
+
get isDisabled() {
|
|
162
|
+
return props.isDisabled;
|
|
163
|
+
},
|
|
164
|
+
get trigger() {
|
|
165
|
+
return props.trigger;
|
|
166
|
+
},
|
|
167
|
+
get shouldCloseOnPress() {
|
|
168
|
+
return props.shouldCloseOnPress;
|
|
169
|
+
},
|
|
170
|
+
get tooltipId() {
|
|
171
|
+
return tooltipId();
|
|
172
|
+
},
|
|
122
173
|
},
|
|
123
174
|
state,
|
|
124
|
-
() => triggerRef
|
|
175
|
+
() => triggerRef,
|
|
125
176
|
);
|
|
126
177
|
|
|
127
178
|
const context: TooltipTriggerContextValue = {
|
|
128
179
|
state,
|
|
129
180
|
tooltipProps,
|
|
181
|
+
setTooltipId,
|
|
130
182
|
triggerRef: () => triggerRef,
|
|
183
|
+
placement: () => props.placement,
|
|
184
|
+
containerPadding: () => props.containerPadding,
|
|
185
|
+
crossOffset: () => props.crossOffset,
|
|
186
|
+
shouldFlip: () => props.shouldFlip,
|
|
187
|
+
isDisabled: () => props.isDisabled,
|
|
131
188
|
};
|
|
132
189
|
|
|
133
|
-
// Clone children and inject trigger props into the first child
|
|
134
190
|
const processChildren = () => {
|
|
135
191
|
const children = props.children;
|
|
136
192
|
if (Array.isArray(children)) {
|
|
137
|
-
// First child is the trigger, rest are tooltip(s)
|
|
138
193
|
const [trigger, ...rest] = children;
|
|
139
194
|
return (
|
|
140
195
|
<>
|
|
141
196
|
<TriggerWrapper
|
|
142
197
|
triggerProps={triggerProps}
|
|
143
|
-
ref={(el) => {
|
|
198
|
+
ref={(el) => {
|
|
199
|
+
triggerRef = el;
|
|
200
|
+
}}
|
|
144
201
|
>
|
|
145
202
|
{trigger}
|
|
146
203
|
</TriggerWrapper>
|
|
@@ -167,13 +224,72 @@ const TriggerWrapper: ParentComponent<{
|
|
|
167
224
|
triggerProps: JSX.HTMLAttributes<HTMLElement>;
|
|
168
225
|
ref: (el: HTMLElement) => void;
|
|
169
226
|
}> = (props) => {
|
|
170
|
-
// Get the child element and clone it with trigger props
|
|
171
227
|
const child = () => props.children as JSX.Element;
|
|
228
|
+
const [triggerElement, setTriggerElement] = createSignal<HTMLElement | null>(null);
|
|
229
|
+
|
|
230
|
+
createEffect(() => {
|
|
231
|
+
const element = triggerElement();
|
|
232
|
+
if (!element) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const triggerProps = props.triggerProps as Record<string, unknown>;
|
|
237
|
+
const describedBy = triggerProps["aria-describedby"] as string | undefined;
|
|
238
|
+
if (describedBy) {
|
|
239
|
+
element.setAttribute("aria-describedby", describedBy);
|
|
240
|
+
} else {
|
|
241
|
+
element.removeAttribute("aria-describedby");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const listeners: Array<[string, EventListener]> = [];
|
|
245
|
+
const eventProps = [
|
|
246
|
+
["onFocus", "focus"],
|
|
247
|
+
["onBlur", "blur"],
|
|
248
|
+
["onPointerEnter", "pointerenter"],
|
|
249
|
+
["onPointerLeave", "pointerleave"],
|
|
250
|
+
["onPointerOver", "pointerover"],
|
|
251
|
+
["onPointerOut", "pointerout"],
|
|
252
|
+
["onMouseEnter", "mouseenter"],
|
|
253
|
+
["onMouseLeave", "mouseleave"],
|
|
254
|
+
["onTouchStart", "touchstart"],
|
|
255
|
+
["onPointerDown", "pointerdown"],
|
|
256
|
+
["onKeyDown", "keydown"],
|
|
257
|
+
] as const;
|
|
258
|
+
|
|
259
|
+
for (const [propName, eventName] of eventProps) {
|
|
260
|
+
const handler = triggerProps[propName];
|
|
261
|
+
if (typeof handler === "function") {
|
|
262
|
+
const listener = handler as EventListener;
|
|
263
|
+
element.addEventListener(eventName, listener);
|
|
264
|
+
listeners.push([eventName, listener]);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
onCleanup(() => {
|
|
269
|
+
for (const [eventName, listener] of listeners) {
|
|
270
|
+
element.removeEventListener(eventName, listener);
|
|
271
|
+
}
|
|
272
|
+
if (describedBy && element.getAttribute("aria-describedby") === describedBy) {
|
|
273
|
+
element.removeAttribute("aria-describedby");
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
});
|
|
172
277
|
|
|
173
278
|
// We wrap in a span with display:contents to not affect layout.
|
|
174
279
|
// However, display:contents makes getBoundingClientRect return zeros,
|
|
175
280
|
// so we pass a ref callback that finds the first actual element child.
|
|
176
281
|
const handleRef = (span: HTMLSpanElement) => {
|
|
282
|
+
const findElementChild = (el: Element): HTMLElement | null => {
|
|
283
|
+
for (const child of el.children) {
|
|
284
|
+
if (child instanceof HTMLElement) {
|
|
285
|
+
return child;
|
|
286
|
+
}
|
|
287
|
+
const found = findElementChild(child);
|
|
288
|
+
if (found) return found;
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
};
|
|
292
|
+
|
|
177
293
|
// Find the first element child that has dimensions (not display:contents)
|
|
178
294
|
const findVisibleChild = (el: Element): HTMLElement | null => {
|
|
179
295
|
if (el instanceof HTMLElement) {
|
|
@@ -181,7 +297,6 @@ const TriggerWrapper: ParentComponent<{
|
|
|
181
297
|
if (rect.width > 0 && rect.height > 0) {
|
|
182
298
|
return el;
|
|
183
299
|
}
|
|
184
|
-
// Check children
|
|
185
300
|
for (const child of el.children) {
|
|
186
301
|
const found = findVisibleChild(child);
|
|
187
302
|
if (found) return found;
|
|
@@ -193,31 +308,28 @@ const TriggerWrapper: ParentComponent<{
|
|
|
193
308
|
// Use requestAnimationFrame to ensure children are rendered and have dimensions
|
|
194
309
|
// This is necessary because SolidJS may not have computed child layout yet
|
|
195
310
|
const resolveRef = () => {
|
|
311
|
+
const elementChild = findElementChild(span);
|
|
196
312
|
const visibleChild = findVisibleChild(span);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
} else {
|
|
200
|
-
// Fallback to span itself
|
|
201
|
-
props.ref(span);
|
|
202
|
-
}
|
|
313
|
+
setTriggerElement(elementChild ?? visibleChild ?? span);
|
|
314
|
+
props.ref(visibleChild ?? elementChild ?? span);
|
|
203
315
|
};
|
|
204
316
|
|
|
205
|
-
|
|
317
|
+
const elementChild = findElementChild(span);
|
|
318
|
+
if (elementChild) {
|
|
319
|
+
setTriggerElement(elementChild);
|
|
320
|
+
}
|
|
321
|
+
|
|
206
322
|
const immediateChild = findVisibleChild(span);
|
|
207
323
|
if (immediateChild) {
|
|
324
|
+
setTriggerElement(elementChild ?? immediateChild);
|
|
208
325
|
props.ref(immediateChild);
|
|
209
326
|
} else {
|
|
210
|
-
// Defer to next frame when layout should be computed
|
|
211
327
|
requestAnimationFrame(resolveRef);
|
|
212
328
|
}
|
|
213
329
|
};
|
|
214
330
|
|
|
215
331
|
return (
|
|
216
|
-
<span
|
|
217
|
-
{...props.triggerProps}
|
|
218
|
-
ref={handleRef}
|
|
219
|
-
style={{ display: 'contents' }}
|
|
220
|
-
>
|
|
332
|
+
<span ref={handleRef} style={{ display: "contents" }}>
|
|
221
333
|
{child()}
|
|
222
334
|
</span>
|
|
223
335
|
);
|
|
@@ -237,30 +349,47 @@ const TriggerWrapper: ParentComponent<{
|
|
|
237
349
|
export function Tooltip(props: TooltipProps): JSX.Element {
|
|
238
350
|
const context = useContext(TooltipTriggerContext);
|
|
239
351
|
|
|
240
|
-
|
|
352
|
+
createEffect(() => {
|
|
353
|
+
context?.setTooltipId(props.id);
|
|
354
|
+
onCleanup(() => {
|
|
355
|
+
context?.setTooltipId(undefined);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
241
359
|
const localState = createTooltipTriggerState({
|
|
242
|
-
get isOpen() {
|
|
243
|
-
|
|
360
|
+
get isOpen() {
|
|
361
|
+
return props.isOpen;
|
|
362
|
+
},
|
|
363
|
+
get defaultOpen() {
|
|
364
|
+
return props.defaultOpen;
|
|
365
|
+
},
|
|
244
366
|
});
|
|
245
367
|
|
|
246
368
|
const state = () => context?.state ?? localState;
|
|
247
|
-
const placement = () => props.placement ??
|
|
369
|
+
const placement = () => props.placement ?? context?.placement() ?? "top";
|
|
370
|
+
const containerPadding = () => props.containerPadding ?? context?.containerPadding() ?? 12;
|
|
371
|
+
const crossOffset = () => props.crossOffset ?? context?.crossOffset() ?? 0;
|
|
372
|
+
const shouldFlip = () => props.shouldFlip ?? context?.shouldFlip() ?? true;
|
|
373
|
+
const offset = () => props.offset ?? 9;
|
|
374
|
+
const arrowSize = () => props.arrowSize ?? 0;
|
|
375
|
+
const arrowBoundaryOffset = () => props.arrowBoundaryOffset ?? 0;
|
|
376
|
+
const isDisabled = () => props.isDisabled ?? context?.isDisabled() ?? false;
|
|
248
377
|
|
|
249
|
-
const isOpen = () => state().isOpen();
|
|
378
|
+
const isOpen = () => !isDisabled() && state().isOpen();
|
|
250
379
|
|
|
251
380
|
// Exit animation state machine: 'closed' | 'open' | 'exiting'
|
|
252
381
|
// Keeps the tooltip mounted during exit animation so CSS transitions can play.
|
|
253
|
-
const [exitState, setExitState] = createSignal<
|
|
254
|
-
isOpen() ?
|
|
382
|
+
const [exitState, setExitState] = createSignal<"closed" | "open" | "exiting">(
|
|
383
|
+
isOpen() ? "open" : "closed",
|
|
255
384
|
);
|
|
256
385
|
|
|
257
386
|
createEffect(() => {
|
|
258
387
|
const open = isOpen();
|
|
259
388
|
const current = exitState();
|
|
260
|
-
if (current ===
|
|
261
|
-
setExitState(
|
|
262
|
-
} else if ((current ===
|
|
263
|
-
setExitState(
|
|
389
|
+
if (current === "open" && !open) {
|
|
390
|
+
setExitState("exiting");
|
|
391
|
+
} else if ((current === "closed" || current === "exiting") && open) {
|
|
392
|
+
setExitState("open");
|
|
264
393
|
}
|
|
265
394
|
});
|
|
266
395
|
|
|
@@ -269,26 +398,32 @@ export function Tooltip(props: TooltipProps): JSX.Element {
|
|
|
269
398
|
|
|
270
399
|
// When exiting, wait for CSS animations to finish, then set state to closed
|
|
271
400
|
createEffect(() => {
|
|
272
|
-
if (exitState() !==
|
|
401
|
+
if (exitState() !== "exiting") return;
|
|
273
402
|
const el = tooltipEl();
|
|
274
|
-
if (!el || !(
|
|
275
|
-
setExitState(
|
|
403
|
+
if (!el || !("getAnimations" in el)) {
|
|
404
|
+
setExitState("closed");
|
|
276
405
|
return;
|
|
277
406
|
}
|
|
278
407
|
const animations = el.getAnimations();
|
|
279
408
|
if (animations.length === 0) {
|
|
280
|
-
setExitState(
|
|
409
|
+
setExitState("closed");
|
|
281
410
|
return;
|
|
282
411
|
}
|
|
283
412
|
let canceled = false;
|
|
284
413
|
Promise.all(animations.map((a) => a.finished))
|
|
285
|
-
.then(() => {
|
|
286
|
-
|
|
287
|
-
|
|
414
|
+
.then(() => {
|
|
415
|
+
if (!canceled) setExitState((s) => (s === "exiting" ? "closed" : s));
|
|
416
|
+
})
|
|
417
|
+
.catch(() => {
|
|
418
|
+
if (!canceled) setExitState((s) => (s === "exiting" ? "closed" : s));
|
|
419
|
+
});
|
|
420
|
+
onCleanup(() => {
|
|
421
|
+
canceled = true;
|
|
422
|
+
});
|
|
288
423
|
});
|
|
289
424
|
|
|
290
|
-
const shouldRender = () => isOpen() || exitState() ===
|
|
291
|
-
const isExiting = () => exitState() ===
|
|
425
|
+
const shouldRender = () => isOpen() || exitState() === "exiting";
|
|
426
|
+
const isExiting = () => exitState() === "exiting";
|
|
292
427
|
|
|
293
428
|
return (
|
|
294
429
|
<Show when={shouldRender()}>
|
|
@@ -297,6 +432,12 @@ export function Tooltip(props: TooltipProps): JSX.Element {
|
|
|
297
432
|
state={state()}
|
|
298
433
|
contextTooltipProps={context?.tooltipProps ?? {}}
|
|
299
434
|
placement={placement()}
|
|
435
|
+
containerPadding={containerPadding()}
|
|
436
|
+
crossOffset={crossOffset()}
|
|
437
|
+
shouldFlip={shouldFlip()}
|
|
438
|
+
offset={offset()}
|
|
439
|
+
arrowSize={arrowSize()}
|
|
440
|
+
arrowBoundaryOffset={arrowBoundaryOffset()}
|
|
300
441
|
triggerRef={context?.triggerRef ?? (() => null)}
|
|
301
442
|
isExiting={isExiting()}
|
|
302
443
|
onTooltipRef={setTooltipEl}
|
|
@@ -311,12 +452,18 @@ export function Tooltip(props: TooltipProps): JSX.Element {
|
|
|
311
452
|
function TooltipContent(
|
|
312
453
|
props: TooltipProps & {
|
|
313
454
|
state: TooltipTriggerState;
|
|
314
|
-
contextTooltipProps: { id?: string };
|
|
315
|
-
placement:
|
|
455
|
+
contextTooltipProps: { readonly id?: string };
|
|
456
|
+
placement: TooltipPlacement;
|
|
457
|
+
containerPadding: number;
|
|
458
|
+
crossOffset: number;
|
|
459
|
+
shouldFlip: boolean;
|
|
460
|
+
offset: number;
|
|
461
|
+
arrowSize: number;
|
|
462
|
+
arrowBoundaryOffset: number;
|
|
316
463
|
triggerRef: () => HTMLElement | null | undefined;
|
|
317
464
|
isExiting: boolean;
|
|
318
465
|
onTooltipRef: (el: HTMLDivElement | null) => void;
|
|
319
|
-
}
|
|
466
|
+
},
|
|
320
467
|
): JSX.Element {
|
|
321
468
|
if (isServer) {
|
|
322
469
|
return null as unknown as JSX.Element;
|
|
@@ -325,15 +472,17 @@ function TooltipContent(
|
|
|
325
472
|
let tooltipRef!: HTMLDivElement;
|
|
326
473
|
const { tooltipProps: ariaTooltipProps } = createTooltip({}, props.state);
|
|
327
474
|
|
|
328
|
-
// Signal to track position styles
|
|
329
475
|
// Start visible at 0,0 and update position asynchronously
|
|
330
476
|
// This ensures the tooltip is immediately accessible (for screen readers and tests)
|
|
331
477
|
// while the visual position gets calculated
|
|
332
478
|
const [positionStyles, setPositionStyles] = createSignal({
|
|
333
|
-
top:
|
|
334
|
-
left:
|
|
335
|
-
visibility:
|
|
479
|
+
top: "0px",
|
|
480
|
+
left: "0px",
|
|
481
|
+
visibility: "visible" as "hidden" | "visible",
|
|
336
482
|
});
|
|
483
|
+
const [renderedPlacement, setRenderedPlacement] = createSignal<TooltipResolvedPlacement>(
|
|
484
|
+
resolvePlacement(props.placement),
|
|
485
|
+
);
|
|
337
486
|
|
|
338
487
|
// Enter animation state: starts true on mount, clears after first animation frame.
|
|
339
488
|
// Uses getAnimations() to detect CSS animations/transitions - if none exist (JSDOM,
|
|
@@ -342,7 +491,7 @@ function TooltipContent(
|
|
|
342
491
|
|
|
343
492
|
createEffect(() => {
|
|
344
493
|
if (!isEntering()) return;
|
|
345
|
-
if (!tooltipRef || !(
|
|
494
|
+
if (!tooltipRef || !("getAnimations" in tooltipRef)) {
|
|
346
495
|
setIsEntering(false);
|
|
347
496
|
return;
|
|
348
497
|
}
|
|
@@ -359,15 +508,21 @@ function TooltipContent(
|
|
|
359
508
|
}
|
|
360
509
|
let canceled = false;
|
|
361
510
|
Promise.all(animations.map((a) => a.finished))
|
|
362
|
-
.then(() => {
|
|
363
|
-
|
|
364
|
-
|
|
511
|
+
.then(() => {
|
|
512
|
+
if (!canceled) setIsEntering(false);
|
|
513
|
+
})
|
|
514
|
+
.catch(() => {
|
|
515
|
+
if (!canceled) setIsEntering(false);
|
|
516
|
+
});
|
|
517
|
+
onCleanup(() => {
|
|
518
|
+
canceled = true;
|
|
519
|
+
});
|
|
365
520
|
});
|
|
366
521
|
|
|
367
522
|
const values = createMemo<TooltipRenderProps>(() => ({
|
|
368
523
|
isEntering: isEntering(),
|
|
369
524
|
isExiting: props.isExiting,
|
|
370
|
-
placement:
|
|
525
|
+
placement: renderedPlacement(),
|
|
371
526
|
}));
|
|
372
527
|
|
|
373
528
|
const renderProps = useRenderProps(
|
|
@@ -375,13 +530,13 @@ function TooltipContent(
|
|
|
375
530
|
class: props.class,
|
|
376
531
|
style: props.style,
|
|
377
532
|
children: props.children,
|
|
378
|
-
defaultClassName:
|
|
533
|
+
defaultClassName: "solidaria-Tooltip",
|
|
379
534
|
},
|
|
380
|
-
values
|
|
535
|
+
values,
|
|
381
536
|
);
|
|
382
537
|
|
|
383
|
-
//
|
|
384
|
-
//
|
|
538
|
+
// Position the overlay in document coordinates, matching React Aria's
|
|
539
|
+
// absolute overlay positioning when the portal container is the document.
|
|
385
540
|
// Returns true if position was successfully updated, false if we need to retry
|
|
386
541
|
const updatePosition = (): boolean => {
|
|
387
542
|
const triggerEl = props.triggerRef();
|
|
@@ -398,45 +553,81 @@ function TooltipContent(
|
|
|
398
553
|
// when the element might be positioned off-screen initially
|
|
399
554
|
const tooltipWidth = tooltipRef.offsetWidth;
|
|
400
555
|
const tooltipHeight = tooltipRef.offsetHeight;
|
|
401
|
-
const
|
|
556
|
+
const viewportWidth = window.innerWidth;
|
|
557
|
+
const viewportHeight = window.innerHeight;
|
|
558
|
+
const containerPadding = props.containerPadding;
|
|
559
|
+
const crossOffset = props.crossOffset;
|
|
560
|
+
const placement = maybeFlipPlacement(
|
|
561
|
+
resolvePlacement(props.placement),
|
|
562
|
+
triggerRect,
|
|
563
|
+
tooltipWidth,
|
|
564
|
+
tooltipHeight,
|
|
565
|
+
viewportWidth,
|
|
566
|
+
viewportHeight,
|
|
567
|
+
containerPadding,
|
|
568
|
+
props.offset,
|
|
569
|
+
props.shouldFlip,
|
|
570
|
+
);
|
|
402
571
|
|
|
403
572
|
let top = 0;
|
|
404
573
|
let left = 0;
|
|
405
574
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2;
|
|
575
|
+
switch (placement) {
|
|
576
|
+
case "top":
|
|
577
|
+
top = triggerRect.top - tooltipHeight - props.offset;
|
|
578
|
+
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2 + crossOffset;
|
|
411
579
|
break;
|
|
412
|
-
case
|
|
413
|
-
top = triggerRect.bottom + offset;
|
|
414
|
-
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2;
|
|
580
|
+
case "bottom":
|
|
581
|
+
top = triggerRect.bottom + props.offset;
|
|
582
|
+
left = triggerRect.left + (triggerRect.width - tooltipWidth) / 2 + crossOffset;
|
|
415
583
|
break;
|
|
416
|
-
case
|
|
417
|
-
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2;
|
|
418
|
-
left = triggerRect.left - tooltipWidth - offset;
|
|
584
|
+
case "left":
|
|
585
|
+
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2 + crossOffset;
|
|
586
|
+
left = triggerRect.left - tooltipWidth - props.offset;
|
|
419
587
|
break;
|
|
420
|
-
case
|
|
421
|
-
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2;
|
|
422
|
-
left = triggerRect.right + offset;
|
|
588
|
+
case "right":
|
|
589
|
+
top = triggerRect.top + (triggerRect.height - tooltipHeight) / 2 + crossOffset;
|
|
590
|
+
left = triggerRect.right + props.offset;
|
|
423
591
|
break;
|
|
424
592
|
}
|
|
425
593
|
|
|
594
|
+
if (placement === "top" || placement === "bottom") {
|
|
595
|
+
left = clamp(
|
|
596
|
+
left,
|
|
597
|
+
triggerRect.left - tooltipWidth + props.arrowSize + props.arrowBoundaryOffset,
|
|
598
|
+
triggerRect.left + triggerRect.width - props.arrowSize - props.arrowBoundaryOffset,
|
|
599
|
+
);
|
|
600
|
+
left = clamp(left, containerPadding, viewportWidth - tooltipWidth - containerPadding);
|
|
601
|
+
} else {
|
|
602
|
+
top = clamp(
|
|
603
|
+
top,
|
|
604
|
+
triggerRect.top - tooltipHeight + props.arrowSize + props.arrowBoundaryOffset,
|
|
605
|
+
triggerRect.top + triggerRect.height - props.arrowSize - props.arrowBoundaryOffset,
|
|
606
|
+
);
|
|
607
|
+
top = clamp(top, containerPadding, viewportHeight - tooltipHeight - containerPadding);
|
|
608
|
+
}
|
|
609
|
+
setRenderedPlacement(placement);
|
|
426
610
|
setPositionStyles({
|
|
427
|
-
top: `${top}px`,
|
|
428
|
-
left: `${left}px`,
|
|
429
|
-
visibility:
|
|
611
|
+
top: `${Math.floor(top + window.scrollY)}px`,
|
|
612
|
+
left: `${Math.floor(left + window.scrollX)}px`,
|
|
613
|
+
visibility: "visible",
|
|
430
614
|
});
|
|
431
615
|
|
|
432
616
|
return true;
|
|
433
617
|
};
|
|
434
618
|
|
|
435
|
-
// Set up positioning
|
|
436
|
-
//
|
|
619
|
+
// Set up positioning and scroll-close effects. Positioning retries while the
|
|
620
|
+
// trigger ref resolves, and pending rAF/setTimeout IDs are canceled on cleanup.
|
|
437
621
|
createEffect(() => {
|
|
438
|
-
|
|
439
|
-
|
|
622
|
+
// Track positioning inputs synchronously so updates from controlled route
|
|
623
|
+
// props reschedule measurement even though layout reads happen in rAF.
|
|
624
|
+
props.placement;
|
|
625
|
+
props.containerPadding;
|
|
626
|
+
props.crossOffset;
|
|
627
|
+
props.shouldFlip;
|
|
628
|
+
props.offset;
|
|
629
|
+
props.arrowSize;
|
|
630
|
+
props.arrowBoundaryOffset;
|
|
440
631
|
|
|
441
632
|
let retryCount = 0;
|
|
442
633
|
const maxRetries = 5;
|
|
@@ -455,30 +646,51 @@ function TooltipContent(
|
|
|
455
646
|
|
|
456
647
|
pendingRaf = requestAnimationFrame(tryUpdatePosition);
|
|
457
648
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
649
|
+
const closeOnScroll = (event: Event) => {
|
|
650
|
+
const trigger = props.triggerRef();
|
|
651
|
+
const target = event.target;
|
|
652
|
+
if (!trigger || (target instanceof Node && !target.contains(trigger))) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
props.state.close(true);
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
window.addEventListener("scroll", closeOnScroll, true);
|
|
663
|
+
window.addEventListener("resize", updatePosition);
|
|
461
664
|
|
|
462
665
|
onCleanup(() => {
|
|
463
666
|
if (pendingRaf) cancelAnimationFrame(pendingRaf);
|
|
464
667
|
if (pendingTimeout) clearTimeout(pendingTimeout);
|
|
465
|
-
window.removeEventListener(
|
|
466
|
-
window.removeEventListener(
|
|
668
|
+
window.removeEventListener("scroll", closeOnScroll, true);
|
|
669
|
+
window.removeEventListener("resize", updatePosition);
|
|
467
670
|
});
|
|
468
671
|
});
|
|
469
672
|
|
|
470
|
-
|
|
471
|
-
const
|
|
673
|
+
const domProps = filterDOMProps(props, { global: true });
|
|
674
|
+
const tooltipId = () => props.contextTooltipProps.id ?? (domProps as { id?: string }).id;
|
|
472
675
|
|
|
473
676
|
// Extract ref from ariaTooltipProps to avoid type conflicts (SolidJS ref types are element-specific)
|
|
474
677
|
const { ref: _ariaRef, ...cleanAriaProps } = ariaTooltipProps as Record<string, unknown>;
|
|
475
678
|
|
|
476
679
|
const setRef = (el: HTMLDivElement) => {
|
|
477
680
|
tooltipRef = el;
|
|
681
|
+
if (!props.dir) {
|
|
682
|
+
el.dir = getDocumentDirection();
|
|
683
|
+
}
|
|
684
|
+
if (!props.lang) {
|
|
685
|
+
const documentLang = document.documentElement.lang;
|
|
686
|
+
const navigatorLang = typeof navigator !== "undefined" ? navigator.language : "";
|
|
687
|
+
el.lang = documentLang.includes("-")
|
|
688
|
+
? documentLang
|
|
689
|
+
: navigatorLang || documentLang || "en-US";
|
|
690
|
+
}
|
|
478
691
|
props.onTooltipRef(el);
|
|
479
692
|
};
|
|
480
693
|
|
|
481
|
-
// Clean up ref on unmount
|
|
482
694
|
onCleanup(() => {
|
|
483
695
|
props.onTooltipRef(null);
|
|
484
696
|
});
|
|
@@ -487,18 +699,18 @@ function TooltipContent(
|
|
|
487
699
|
<OverlayContainer>
|
|
488
700
|
<div
|
|
489
701
|
{...domProps}
|
|
490
|
-
{...props.contextTooltipProps}
|
|
491
702
|
{...cleanAriaProps}
|
|
703
|
+
id={tooltipId()}
|
|
492
704
|
role="tooltip"
|
|
493
705
|
ref={setRef}
|
|
494
706
|
class={renderProps.class()}
|
|
495
707
|
style={{
|
|
496
|
-
position:
|
|
497
|
-
|
|
708
|
+
position: "absolute",
|
|
709
|
+
"z-index": 100000,
|
|
498
710
|
...positionStyles(),
|
|
499
711
|
...renderProps.style(),
|
|
500
712
|
}}
|
|
501
|
-
data-placement={
|
|
713
|
+
data-placement={renderedPlacement()}
|
|
502
714
|
data-entering={isEntering() || undefined}
|
|
503
715
|
data-exiting={props.isExiting || undefined}
|
|
504
716
|
>
|
|
@@ -508,5 +720,61 @@ function TooltipContent(
|
|
|
508
720
|
);
|
|
509
721
|
}
|
|
510
722
|
|
|
511
|
-
|
|
723
|
+
function resolvePlacement(placement: TooltipPlacement): TooltipResolvedPlacement {
|
|
724
|
+
if (placement === "start") {
|
|
725
|
+
return getDocumentDirection() === "rtl" ? "right" : "left";
|
|
726
|
+
}
|
|
727
|
+
if (placement === "end") {
|
|
728
|
+
return getDocumentDirection() === "rtl" ? "left" : "right";
|
|
729
|
+
}
|
|
730
|
+
return placement;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function getDocumentDirection(): "ltr" | "rtl" {
|
|
734
|
+
if (typeof document === "undefined") {
|
|
735
|
+
return "ltr";
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return document.documentElement.dir === "rtl" ? "rtl" : "ltr";
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function maybeFlipPlacement(
|
|
742
|
+
placement: TooltipResolvedPlacement,
|
|
743
|
+
triggerRect: DOMRect,
|
|
744
|
+
tooltipWidth: number,
|
|
745
|
+
tooltipHeight: number,
|
|
746
|
+
viewportWidth: number,
|
|
747
|
+
viewportHeight: number,
|
|
748
|
+
containerPadding: number,
|
|
749
|
+
offset: number,
|
|
750
|
+
shouldFlip: boolean,
|
|
751
|
+
): TooltipResolvedPlacement {
|
|
752
|
+
if (!shouldFlip) {
|
|
753
|
+
return placement;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
switch (placement) {
|
|
757
|
+
case "top":
|
|
758
|
+
return triggerRect.top - tooltipHeight - offset < containerPadding ? "bottom" : placement;
|
|
759
|
+
case "bottom":
|
|
760
|
+
return triggerRect.bottom + tooltipHeight + offset > viewportHeight - containerPadding
|
|
761
|
+
? "top"
|
|
762
|
+
: placement;
|
|
763
|
+
case "left":
|
|
764
|
+
return triggerRect.left - tooltipWidth - offset < containerPadding ? "right" : placement;
|
|
765
|
+
case "right":
|
|
766
|
+
return triggerRect.right + tooltipWidth + offset > viewportWidth - containerPadding
|
|
767
|
+
? "left"
|
|
768
|
+
: placement;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function clamp(value: number, min: number, max: number): number {
|
|
773
|
+
if (max < min) {
|
|
774
|
+
return min;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return Math.min(Math.max(value, min), max);
|
|
778
|
+
}
|
|
779
|
+
|
|
512
780
|
export type { TooltipTriggerState };
|